From a3dcacbfbf8e189f66e7ce71e720fcf28edf9b6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A7=E7=8E=8B=E5=8F=AB=E6=88=91=E6=9D=A5=E5=B7=A1?= =?UTF-8?q?=E5=B1=B1?= Date: Thu, 5 Mar 2026 01:52:32 +0100 Subject: [PATCH 01/20] Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (467 of 467 strings) Translation: ntfy/Android app Translate-URL: https://hosted.weblate.org/projects/ntfy/android/zh_Hans/ --- app/src/main/res/values-zh-rCN/strings.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index c1d90d31..bf4ef4a6 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -471,4 +471,10 @@ 在通知中搜索 重置为默认 输入有效的服务 URL,如 https://ntfy.example.com + 连接丢失 + 已无法连接到 %1$s 超过 %2$d 分钟 + 已无法连接到 %1$d 台服务器超过 %2$d 分钟 + 关闭 + 延后 1 小时 + 永不显示 From 104f5e865e34b5c43d2ae52283a00080e6857837 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Wed, 4 Mar 2026 20:51:54 +0100 Subject: [PATCH 02/20] Translated using Weblate (Estonian) Currently translated at 100.0% (467 of 467 strings) Translation: ntfy/Android app Translate-URL: https://hosted.weblate.org/projects/ntfy/android/et/ --- app/src/main/res/values-et/strings.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index e46f2ad4..0d9b6459 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -461,4 +461,10 @@ Otsi teavitustest Taasta algväärtused Sisesta korrektne teenuse võrguaadress, nt https://ntfy.toredomeen.com + Ühendus on katkenud + Ühendus %1$s teenusega toimib vaid %2$d minuti(t) + Ühendus %1$d serveriga toimib vaid %2$d minuti(t) + Loobu + Tukasta 1t + Ära näita iialgi From 6767908c12a6b4952fbf7454986a75e1b2edcd04 Mon Sep 17 00:00:00 2001 From: Kachelkaiser Date: Wed, 4 Mar 2026 16:30:31 +0100 Subject: [PATCH 03/20] Translated using Weblate (German) Currently translated at 100.0% (467 of 467 strings) Translation: ntfy/Android app Translate-URL: https://hosted.weblate.org/projects/ntfy/android/de/ --- app/src/main/res/values-de/strings.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 5067c2dc..41d8ddb6 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -471,4 +471,10 @@ In Benachrichtigungen suchen Eine gültige Service-URL eingeben, z. B. https://ntfy.example.com Benachrichtigungen suchen + Verbindung verloren + Es konnte länger als %2$d Minuten lang keine Verbindung zu %1$s hergestellt werden + Es konnte länger als %2$d Minuten lang keine Verbindung zu %1$d Servern hergestellt werden + Verwerfen + 1 Stunde schlummern + Niemals zeigen From d8fab95b91c3178a7fbb96e294df18f90f2929a6 Mon Sep 17 00:00:00 2001 From: Shoshin Akamine Date: Fri, 6 Mar 2026 06:02:09 +0100 Subject: [PATCH 04/20] Translated using Weblate (Japanese) Currently translated at 100.0% (467 of 467 strings) Translation: ntfy/Android app Translate-URL: https://hosted.weblate.org/projects/ntfy/android/ja/ --- app/src/main/res/values-ja/strings.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index c2a22474..aea03527 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -471,4 +471,10 @@ 通知内容を検索します 既定にリセット 有効なサービスURLを入力してください、例: https://ntfy.example.com + 接続が切断されました + %2$d 分以上 %1$s に接続できませんでした + %1$d 個のサーバーに %2$d 分以上接続できませんでした + 了解 + 1時間後に再通知 + 再表示しない From 5172f01342e0e639a5ef30b76bd8023c74479506 Mon Sep 17 00:00:00 2001 From: 109247019824 <109247019824@users.noreply.hosted.weblate.org> Date: Sat, 7 Mar 2026 20:25:46 +0100 Subject: [PATCH 05/20] Translated using Weblate (Bulgarian) Currently translated at 100.0% (467 of 467 strings) Translation: ntfy/Android app Translate-URL: https://hosted.weblate.org/projects/ntfy/android/bg/ --- app/src/main/res/values-bg/strings.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index 25bed2e1..ce5e0baa 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -471,4 +471,10 @@ Търсене в известията Подразбирани настройки Въведете адрес на услуга, напр. https://ntfy.example.com + Връзката е загубена + От най-малко %2$d минути няма връзка с %1$s + От най-малко %2$d минути няма връзка със сървърите на %1$d + Отхвърляне + Отлагане с 1ч + Да не се показва повече From 1da54ec2bb4d3ed2748879ad3f0d106b0125dba1 Mon Sep 17 00:00:00 2001 From: ezn24 Date: Sun, 8 Mar 2026 13:54:26 +0100 Subject: [PATCH 06/20] Translated using Weblate (Chinese (Traditional Han script)) Currently translated at 100.0% (467 of 467 strings) Translation: ntfy/Android app Translate-URL: https://hosted.weblate.org/projects/ntfy/android/zh_Hant/ --- app/src/main/res/values-zh-rTW/strings.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index b272e0b7..77191ab5 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -471,4 +471,10 @@ 通知中的搜尋 重設為預設值 請輸入有效的服務 URL,例如:https://ntfy.example.com + 連線已中斷 + 超過 %2$d 分鐘無法連線至 %1$s + 超過 %2$d 分鐘無法連線至 %1$d 個伺服器 + 關閉 + 延後 1 小時 + 永不顯示 From d47150f21293cf1e2c9d3885eba239083eed67a4 Mon Sep 17 00:00:00 2001 From: Patrick van Halderen Date: Sun, 8 Mar 2026 22:43:01 +0100 Subject: [PATCH 07/20] Translated using Weblate (Dutch) Currently translated at 88.8% (415 of 467 strings) Translation: ntfy/Android app Translate-URL: https://hosted.weblate.org/projects/ntfy/android/nl/ --- app/src/main/res/values-nl/strings.xml | 61 ++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 27e6ed8e..a566e76d 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -364,4 +364,65 @@ Fout bij publiceren: %1$s Kon bericht niet publiceren: %1$s (code %2$d) Bericht gepubliceerd + Terug naar standaardinstellingen + Geldig vanaf + Uitgever + SHA-256 vingerafdruk + Onderwerp + Onjuiste server URL + Vul de server URL in waar dit certificaat voor moet worden gebruikt, en het wachtwoord voor het PKCS#12 bestand. + Beoordeel de certificaat instellingen en sla op om het certificaat toe te voegen. Dit certificaat zal worden gebruikt om te authenticeren met de server. + Wachtwoord + Onjuist wachtwoord of onjuist PKCS#12 bestand + Onjuist wachtwoord of beschadigd PKCS#12 bestand + Verbinding verbroken + Kan geen verbinding maken met %1$s voor meer dan %2$d minuten + Kan geen verbinding maken met %1$d servers voor meer dan %2$d minuten + Afwijzen + Sluimer voor 1 uur + Nooit laten zien + Publieer naar %1$s + Uploaden: %1$s (%2$s / %3$s) + Upload geannuleerd + Titel + Labels + Prioriteit + Klik URL + Email + Vertraging + Markdown + Insluiten via URL + Bestand insluiten + Telefoongesprek + Klik URL + bijvoorbeeld https://example.com/alerts/1234 + Email + bijvoorbeeld jan@jansen.nl + Vertraging bezorging + Attachment URL + bijvoorbeeld https://example.com/flowers.jpg + Attachment bestandsnaam + bijvoorbeeld lilies.jpg + Telefoongesprek + bijvoorbeel +1234567890 + Bekijk de documentatie voor voorbeelden en een uitgebreide beschrijving van alle publiceer-functies. + Berichtenbalk + Publiceer bericht + Meer opties + Fout bij verbinden + Er was een probleem bij het verbinden met %1$s. De app zal blijven proberen in de achtergrond. + Verbinding geweigerd. De server is mogelijk niet bereikbaar of het adres is onjuist. + WebSocket niet ondersteund. De server ondersteund mogelijk geen WebSochet, of het adres is onjuist. + Niet toegestaan. De server gaf een HTTP 401/403 antwoord. Controleer uw gebruikersnaam en/of wachtwoord. + Probeer opnieuw + Nieuwe poging in %1$d… + Opnieuw proberen… + Taal + Systeemstandaarden worden gebruikt + Systeemstandaarden + Dynamische kleuren + Dynamische systeemkleuren worden gebruikt + Ntfy thema kleuren worden gebruikt + Toon berichtenbalk + Berichtenbalk getoond onderaan onderwerpweergave From 759a97fe0ed8e8c83e9a6313369403a3ad7c9446 Mon Sep 17 00:00:00 2001 From: BonifacioCalindoro Date: Sat, 14 Mar 2026 00:59:55 +0100 Subject: [PATCH 08/20] Translated using Weblate (Spanish) Currently translated at 100.0% (467 of 467 strings) Translation: ntfy/Android app Translate-URL: https://hosted.weblate.org/projects/ntfy/android/es/ --- app/src/main/res/values-es/strings.xml | 72 +++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 8df653cc..2151e87c 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -342,8 +342,8 @@ Descartar Conceder ahora Alarmas exactas - ntfy puede programar alarmas exactas (utilizadas para reconectar WebSockets en segundo plano) - ntfy no puede programar alarmas exactas. Haga clic para conceder el permiso ahora + ntfy puede programar alarmas exactas. Las alarmas exactas son requeridas para reconectar WebSockets en segundo plano. Haga clic para revocar este permiso. + ntfy no puede programar alarmas exactas. Las alarmas exactas son requeridas para reconectar WebSockets en segundo plano. Haga clic para conceder el permiso. Publicar a %1$s Título p.e. Alguien está en la puerta @@ -409,4 +409,72 @@ ej. https://ntfy.example.com Subject Error de conexión + Restaurar valores por defecto + Conexión perdida + No se pudo conectar a %1$s por más de %2$d minutos + No se pudo conectar a %1$d servidores por más de %2$d minutos + Ignorar + Suspender 1h + Nunca mostrar + Su búsqueda no tuvo ningún resultado + Buscar notificaciones + Buscar en notificaciones + ej. alerta, calavera + Email + Markdown + Email + ej. phil@example.com + ej. 30m, 1h, today 9pm (Siempre en inglés) + ej. https://example.com/flowers.jpg + ej. ilies.jpg + ej. +1234567890 + Error de conexión + Hubo un problema al conectar a %1$s. La aplicación continuará intentando reconectar en segundo plano. + Conexión rechazada. Puede que el servidor esté desconectado o la dirección sea incorrecta. + WebSocket no soportado. Puede que el servidor no soporte conexiones WebSocket, o que la dirección sea incorrecta. + No autorizado. El servidor devolvió una respuesta HTTP 401/403. Por favor, compruebe su usuario y contraseña. + Reintentar ahora + Reintentando en %1$ds… + Reintentando… + Certificado fijado, emitido por %1$s, expira %2$s, usado para conexiones a %3$s + Certificado fijado, emitido por %1$s, expirado, usado para conexiones a %2$s + Añadir un certificado de confianza + Importa un certificado en el almacén de confianza (PEM). Al conectarse al servidor ntfy, se confiará en este certificado. + Certificados de cliente (mTLS) + Certificado de cliente, emitido por %1$s, expira %2$s, usado para conectar a %3$s + Certificado de cliente, emitido por %1$s, expirado, usado para conectar a %2$s + Añadir un certificado de cliente + Importar certificado para autenticación TLS mutua (PKCS#12). Este certificado se utilizará al conectarse al servidor. + Archivo de certificado no válido + Archivo PKCS#12 inválido + No se puede añadir el usuario si se ha configurado una cabecera Authorization personalizada para este servidor + Introduce una URL del servicio válida, ej. (https://ntfy.example.com) + Añadir encabezado personalizado + Editar encabezado personalizado + Nombre de la cabecera (ej. CF-Access-Client-Id) + Valor de la cabecera (ej. 9f3c2e4a1b2d4e) + Añade una cabecera HTTP personalizada que se enviará con cada solicitud al servidor especificado. + Puedes editar el nombre o el valor de la cabecera seleccionada, o eliminarla. + El nombre de la cabecera contiene caracteres no válidos + Detalles del certificado + Advertencia de seguridad + Añadir certificado de confianza + Tu conexión no es privada + El certificado del servidor no es de confianza. Es posible que atacantes estén intentando robar tu información. No continúes a menos que sepas por qué este certificado no es de confianza. + Has seleccionado un archivo de certificado. Revisa los detalles a continuación antes de añadirlo a tus certificados de confianza. + Este certificado se utiliza para las conexiones a la URL del servicio indicada abajo. Has añadido esta excepción manualmente. + Introduce la URL del servicio a la que debe fijarse este certificado. El certificado solo será de confianza para esta URL. + Advertencia: Este certificado ha caducado. + Advertencia: Este certificado aún no es válido. + URL no válida + No se puede cargar el certificado: %1$s + Confiar + Certificado de cliente + Añadir certificado de cliente + Introduce la URL del servicio para la que debe usarse este certificado y la contraseña del archivo PKCS#12. + Revisa los detalles del certificado y guarda para añadir este certificado de cliente. Este certificado se utilizará para autenticarse con el servidor. + Contraseña + Contraseña incorrecta o archivo PKCS#12 no válido + Contraseña no válida o archivo PKCS#12 dañado + URL del servicio no válida From 789e34b3cf52cb2fef25d609493185a3180b0d75 Mon Sep 17 00:00:00 2001 From: Liviu Roman Date: Tue, 17 Mar 2026 10:58:16 +0100 Subject: [PATCH 09/20] Translated using Weblate (Romanian) Currently translated at 71.7% (335 of 467 strings) Translation: ntfy/Android app Translate-URL: https://hosted.weblate.org/projects/ntfy/android/ro/ --- app/src/main/res/values-ro/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index ec8b43f6..7b23b0bd 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -343,4 +343,7 @@ Alarme exacte ntfy poate programa alarme exacte. Alarmele exacte sunt necesare pentru a reconecta WebSocket-urile în fundal. Faceți clic pentru a revoca permisiunea. ntfy nu poate programa alarme exacte. Alarmele exacte sunt necesare pentru a reconecta WebSockets în fundal. Faceți clic pentru a acorda permisiunea. + Următorul + Adaugă + Şterge From 3b9b4b7fb3d209f9dbfde7b52a7486b6ca0d33e2 Mon Sep 17 00:00:00 2001 From: Guillaume Petit Date: Wed, 18 Mar 2026 17:55:53 +0100 Subject: [PATCH 10/20] Translated using Weblate (French) Currently translated at 100.0% (467 of 467 strings) Translation: ntfy/Android app Translate-URL: https://hosted.weblate.org/projects/ntfy/android/fr/ --- app/src/main/res/values-fr/strings.xml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index c57b708c..c4b04a7e 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -347,10 +347,10 @@ Couleurs dynamiques Utiliser les couleurs de thème ntfy Utiliser les couleurs dynamiques du système - e.g. Quelqu\'un est à la porte + par ex. Quelqu\'un est à la porte Message Étiquettes - e.g. warning, skull + par ex. attention, crâne Priorité Publier Impossible de publier le message : %1$s @@ -390,7 +390,7 @@ Afficher la barre de message Barre de message affichée en bas du sujet Bouton Publier affiché en bas du sujet - En-têtes HTTP personnalisés + En-têtes personnalisés Définissez des en-têtes HTTP personnalisés qui seront envoyés avec chaque demande, par exemple, si votre serveur ntfy se trouve derrière un proxy ou un tunnel authentifié. Ajouter un en-tête Ajouter un en-tête pour un serveur @@ -471,4 +471,10 @@ Rechercher dans les notifications Réinitialiser par défaut Entrez une URL de service valide, par ex. https://ntfy.example.com + Connexion perdue + Impossible de se connecter à %1$s depuis plus de %2$d minutes + Impossible de se connecter à %1$d serveurs depuis plus de %2$d minutes + Ignorer + Sourdine 1h + Ne jamais montrer From 72ca4b111b9e844078325246faadee2c6b464d49 Mon Sep 17 00:00:00 2001 From: Cairo Braga Date: Tue, 31 Mar 2026 07:56:14 +0200 Subject: [PATCH 11/20] Translated using Weblate (Portuguese) Currently translated at 76.4% (357 of 467 strings) Translation: ntfy/Android app Translate-URL: https://hosted.weblate.org/projects/ntfy/android/pt/ --- app/src/main/res/values-pt/strings.xml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 64dad04a..b3b6fb93 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -345,4 +345,26 @@ Alarmes exactos O ntfy pode agendar alarmes exatos. Alarmes exatos são necessários para reconectar os WebSockets em segundo plano. Clique para revogar a permissão. O ntfy não pode agendar alarmes exatos. Alarmes exatos são necessários para reconectar os WebSockets em segundo plano. Clique para conceder a permissão. + Próximo + Adicionar + Apagar + Restaurar valores padrão + ex. https://ntfy.example.com + Assunto + Emissor + Impressão digital SHA-256 + Válido desde + Válido até + Certificado adicionado + Certificado apagado + Conexão perdida + ex. Alguém está à porta + Mensagem + Etiquetas + ex. aviso, perigo + Prioridade + Publicar + Não foi possível publicar a mensagem: %1$s + Não foi possível publicar a mensagem: %1$s (código %2$d) + Mensagem publicada From 26cc801055222b19066e98978de6f2d528f3e525 Mon Sep 17 00:00:00 2001 From: Daniel Maganha Date: Tue, 31 Mar 2026 06:41:19 +0200 Subject: [PATCH 12/20] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (3 of 3 strings) Translation: ntfy/Android app (Fastlane metadata) Translate-URL: https://hosted.weblate.org/projects/ntfy/android-fastlane/pt_BR/ --- fastlane/metadata/android/pt-BR/title.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastlane/metadata/android/pt-BR/title.txt b/fastlane/metadata/android/pt-BR/title.txt index eca89a34..ec9f1981 100644 --- a/fastlane/metadata/android/pt-BR/title.txt +++ b/fastlane/metadata/android/pt-BR/title.txt @@ -1 +1 @@ -ntfy - PUT/POST ao seu disp. +ntfy - PUT/POST ao dispositivo From 1c0b733b309b8572f5077f63786276c01c36fb7e Mon Sep 17 00:00:00 2001 From: Daniel Maganha Date: Tue, 31 Mar 2026 01:42:17 +0200 Subject: [PATCH 13/20] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (467 of 467 strings) Translation: ntfy/Android app Translate-URL: https://hosted.weblate.org/projects/ntfy/android/pt_BR/ --- app/src/main/res/values-pt-rBR/strings.xml | 132 +++++++++++++++++++++ 1 file changed, 132 insertions(+) diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index cae5a5c0..3b72fca0 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -341,4 +341,136 @@ Alarmes exatos O ntfy pode agendar alarmes exatos, eles são usados para reconectar WebSockets em segundo plano. Clique para revogar a permissão. O ntfy não pode agendar alarmes exatos, eles são usados para reconectar WebSockets em segundo plano. Clique para conceder a permissão. + Próximo + Adicionar + Apagar + Restaurar Padrões + ex. https://ntfy.example.com + Dono + Emissor + Assinatura SHA-256 + Válido de + Válido até + Certificado adicionado + Certificado removido + Notificação de alerta de conexão + Não foi possível conectar à %1$s por mais de %2$d minutos + Não foi possível conectar aos servidores %1$d por mais de %2$d minutos + Dispensar + Adiar 1h + Nunca mostrar + Erro de conexão + Certificado do servidor não confiável + Sua busca não retornou resultados + Notificações de pesquisa + Buscar em notificações + Publicar em %1$s + Título + ex. Tem alguém na porta + Mensagem + Tags + ex. Alerta, skull + Prioridade + Publicar + Não foi possível publicar a mensagem: %1$s + Não foi possível publicar a mensagem: %1$s (código %2$d) + Mensagem publicada + Subindo: %1$s (%2$s / %3$s) + Upload cancelado + Título + Etiquetas + Prioridade + Click URL + Email + Atraso + Markdown + Anexar com URL + Anexar arquivo local + Chamada telefônica + Click URL + ex. https://example.com/alerts/1234 + Email + ex. phil@example.com + Atraso na entrega + URL do anexo + ex. https://example.com/flowers.jpg + Nome de arquivo do anexo + ex. lilies.jpg + Chamada telefônica + ex. +1234567890 + Para exemplos e uma descrição detalhada de todos os recursos de publicação, por favor consulte documentation. + Barra de mensagens + Publicar mensagem + Mais opções + Confie + Certificado cliente + Adicionar certificado cliente + Insira a URL de serviço para o qual este certificado deve ser usado e a senha para o arquivo PKCS#12. + Revisar os detalhes do certificado e salvar para adicionar este certificado de cliente. Este certificado será usado para autenticar com o servidor. + Senha + Senha incorreta ou arquivo PKCS#12 inválido + Senha inválida ou arquivo PKCS#12 corrompido + URL de serviço inválida + ex: 30m, 1h, today 9pm (expressões de tempo apenas em inglês) + Publicar notificação + Falha na conexão + Houve um problema ao conectar em %1$s. O app vai continuar tentando reconectar em segundo plano. + Conexão recusada. O servidor pode estar desligado ou o endereço pode estar incorreto. + WebSocket não suportado. O servidor pode não suportar conexões WebSocket ou o endereço pode estar incorreto. + Não autorizado. O servidor retornou uma resposta HTTP 401/403. Favor verificar se seu usuário e senha estão corretos. + Tentar de novo agora + Tentando de novo em %1$d… + Tentando de novo… + Idioma + Usando padrão do sistema + Padrão do sistema + Cores dinâmicas + Usando as cores dinâmicas do sistema + Usando as cores do tema ntfy + Mostrar barra de mensagens + Barra de mensagens mostrada abaixo da visualização de tópico + Botão publicar mostrado abaixo da visualização de tópico + Cabeçalhos customizados + Definir cabeçalhos HTTP personalizados que serão enviados junto com cada requisição, por ex. se seu servidor ntfy está atrás de um túnel ou proxy autenticado. + Adicionar cabeçalho + Adicionar um cabeçalho para um servidor + Cabeçalhos são incluídos em cada requisição HTTP. Cada servidor ntfy pode ter seu próprio conjunto de cabeçalhos personalizados. + Gerenciar certificados + Adicionar certificados para a trust store e gerenciar certificados de cliente para mTLS + Certificados confiáveis + Certificado fixado, emitido por %1$s,expira em %2$s, usado para conexões em %3$s + Certificado fixado, emitido por %1$s, expiradom usado para conexões em %2$s + Adicionar um certificado confiável + Importar um certificado para a trust store (PEM). Ao conectar ao servidor ntfy, este certificado será considerado confiável. + Certificado de cliente (mTLS) + Certificado de cliente, emitido por %1$s, expira em %2$s, usado para conexões com %3$s + Certificado de cliente, emitido por %1$s, expirado, usado para conexões com%2$s + Adicionar um certificado de cliente + Importar certificado para autenticação TLS mútua (PKCS#12). Este certificado será usado ao conectar com o servidor. + Arquivo de certificado inválido + Arquivo PKCS#12 inválido + Não é possível adicionar usuário se o cabeçalho de autorização personalizada está configurado para este servidor + Insira uma URL de serviço válida, por ex. https://ntfy.example.com + Adicionar cabeçalho personalizado + Editar cabeçalho personalizado + Nome do cabeçalho (ex. CF-Access-Client-Id) + Valor do cabeçalho (ex. 9f3c2e4a1b2d4e) + Adicionar um cabeçalho HTTP personalizado que será enviado com cada requisição para o servidor especificado. + Você pode editar o nome/valor do cabeçalho para o cabeçalho selecionado, ou apaga-lo. + O nome do cabeçalho contém caracteres inválidos + Este cabeçalho está reservado e configurado pelo ntfy + Não é possível adicionar um cabeçalho de autorização se um usuário está configurado para este servidor + Um cabeçalho com este nome já existe para este servidor + Detalhes do certificado + Alerta de segurança + Adicionar certificado confiável + Sua conexão não é privada + O certificado do servidor não é confiável. Atacantes podem estar tentando roubar suas informações. Não prossiga a menos que você saiba porque este certificado não é confiável. + Você selecionou um arquivo de certificado. Revise os detalhes abaixo antes de adiciona-lo aos seus certificados confiáveis. + Este certificado é usado para conexões à URL de serviço abaixo. Você adicionou esta exceção manualmente. + Insira a URL de serviço para o qual este certificado deveria ser fixado. O certificado será considerado confiável apenas para esta URL. + Alerta: Este certificado expirou. + Alerta: Este certificado ainda não é válido. + URL inválida + Não foi possível carregar certificado: %1$s From 659378f849d92099d8251dbf3aea870a50ebf7ce Mon Sep 17 00:00:00 2001 From: Cairo Braga Date: Tue, 31 Mar 2026 07:42:34 +0200 Subject: [PATCH 14/20] Translated using Weblate (Spanish) Currently translated at 100.0% (467 of 467 strings) Translation: ntfy/Android app Translate-URL: https://hosted.weblate.org/projects/ntfy/android/es/ --- app/src/main/res/values-es/strings.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 2151e87c..66e7ac1f 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -141,8 +141,8 @@ Descargar automáticamente archivos adjuntos de hasta %1$s Nunca descargar nada automáticamente Siempre descargar todo automáticamente - Si es más pequeño que 100 KB - Si es más pequeño que 500 KB + Si es inferior a 100 kB + Si es inferior a 500 kB Si es más pequeño que 5 MB Eliminar notificaciones Nunca eliminar automáticamente las notificaciones @@ -346,7 +346,7 @@ ntfy no puede programar alarmas exactas. Las alarmas exactas son requeridas para reconectar WebSockets en segundo plano. Haga clic para conceder el permiso. Publicar a %1$s Título - p.e. Alguien está en la puerta + ej. Alguien está en la puerta Mensaje Etiquetas Prioridad @@ -407,7 +407,7 @@ Añadir certificados a la lista de confiados y administrar certificados de cliente para mTLS Certificados confiados ej. https://ntfy.example.com - Subject + Asunto Error de conexión Restaurar valores por defecto Conexión perdida @@ -419,7 +419,7 @@ Su búsqueda no tuvo ningún resultado Buscar notificaciones Buscar en notificaciones - ej. alerta, calavera + ej. alerta, peligro Email Markdown Email From b5f7a634c01201c72ee20d28f78568a20d4e0210 Mon Sep 17 00:00:00 2001 From: Philipp Heckel Date: Wed, 1 Apr 2026 20:44:23 -0400 Subject: [PATCH 15/20] Connection lost v2 --- app/src/main/AndroidManifest.xml | 6 +- .../java/io/heckel/ntfy/app/Application.kt | 21 ++++ .../java/io/heckel/ntfy/backup/Backuper.kt | 5 + .../main/java/io/heckel/ntfy/db/Repository.kt | 28 ++++- .../heckel/ntfy/service/SubscriberService.kt | 101 +++++++++--------- .../ntfy/service/SubscriberServiceManager.kt | 21 +++- .../java/io/heckel/ntfy/ui/MainActivity.kt | 47 ++++++++ .../io/heckel/ntfy/ui/SettingsActivity.kt | 26 +++++ app/src/main/res/layout/activity_main.xml | 18 +++- app/src/main/res/values-bg/strings.xml | 4 +- app/src/main/res/values-de/strings.xml | 4 +- app/src/main/res/values-es/strings.xml | 4 +- app/src/main/res/values-et/strings.xml | 4 +- app/src/main/res/values-fr/strings.xml | 4 +- app/src/main/res/values-ja/strings.xml | 3 - app/src/main/res/values-nl/strings.xml | 4 +- app/src/main/res/values-zh-rCN/strings.xml | 4 +- app/src/main/res/values-zh-rTW/strings.xml | 4 +- app/src/main/res/values/strings.xml | 24 ++++- app/src/main/res/values/values.xml | 19 ++++ app/src/main/res/xml/main_preferences.xml | 6 ++ .../metadata/android/en-US/changelog/NEXT.txt | 3 + 22 files changed, 270 insertions(+), 90 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelog/NEXT.txt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6ca9d3c0..f2df392b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ + @@ -131,15 +132,14 @@ android:enabled="true" android:exported="false"/> - + - - + diff --git a/app/src/main/java/io/heckel/ntfy/app/Application.kt b/app/src/main/java/io/heckel/ntfy/app/Application.kt index d594d670..bc06f046 100644 --- a/app/src/main/java/io/heckel/ntfy/app/Application.kt +++ b/app/src/main/java/io/heckel/ntfy/app/Application.kt @@ -1,8 +1,13 @@ package io.heckel.ntfy.app import android.app.Application +import android.net.ConnectivityManager +import android.net.Network +import android.net.NetworkCapabilities +import android.net.NetworkRequest import com.google.android.material.color.DynamicColors import io.heckel.ntfy.db.Repository +import io.heckel.ntfy.service.SubscriberServiceManager import io.heckel.ntfy.util.Log import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -24,5 +29,21 @@ class Application : Application() { if (repository.getDynamicColorsEnabled()) { DynamicColors.applyToActivitiesIfAvailable(this) } + registerNetworkCallback() + } + + private fun registerNetworkCallback() { + val connectivityManager = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager + val networkRequest = NetworkRequest.Builder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .build() + connectivityManager.registerNetworkCallback(networkRequest, object : ConnectivityManager.NetworkCallback() { + override fun onAvailable(network: Network) { + SubscriberServiceManager.refresh(this@Application) + } + override fun onLost(network: Network) { + SubscriberServiceManager.refresh(this@Application) + } + }) } } diff --git a/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt b/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt index 2dafa839..2bb28909 100644 --- a/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt +++ b/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt @@ -90,6 +90,9 @@ class Backuper(val context: Context) { if (settings.mutedUntil != null) { repository.setGlobalMutedUntil(settings.mutedUntil) } + if (settings.connectionAlertSeconds != null) { + repository.setConnectionAlertSeconds(settings.connectionAlertSeconds) + } if (settings.lastSharedTopics != null) { settings.lastSharedTopics.forEach { repository.addLastShareTopic(it) } } @@ -278,6 +281,7 @@ class Backuper(val context: Context) { recordLogs = repository.getRecordLogs(), defaultBaseUrl = repository.getDefaultBaseUrl() ?: "", mutedUntil = repository.getGlobalMutedUntil(), + connectionAlertSeconds = repository.getConnectionAlertSeconds(), lastSharedTopics = repository.getLastShareTopics() ) } @@ -421,6 +425,7 @@ data class Settings( val recordLogs: Boolean?, val defaultBaseUrl: String?, val mutedUntil: Long?, + val connectionAlertSeconds: Long?, val lastSharedTopics: List?, ) diff --git a/app/src/main/java/io/heckel/ntfy/db/Repository.kt b/app/src/main/java/io/heckel/ntfy/db/Repository.kt index a6716c74..fc3dd561 100644 --- a/app/src/main/java/io/heckel/ntfy/db/Repository.kt +++ b/app/src/main/java/io/heckel/ntfy/db/Repository.kt @@ -440,8 +440,18 @@ class Repository(private val sharedPrefs: SharedPreferences, database: Database) } } + fun getConnectionAlertSeconds(): Long { + return sharedPrefs.getLong(SHARED_PREFS_CONNECTION_ALERT_SECONDS, CONNECTION_ALERT_DEFAULT) + } + + fun setConnectionAlertSeconds(seconds: Long) { + sharedPrefs.edit { + putLong(SHARED_PREFS_CONNECTION_ALERT_SECONDS, seconds) + } + } + fun getConnectionAlertSnoozeUntil(): Long { - return sharedPrefs.getLong(SHARED_PREFS_CONNECTION_ALERT_SNOOZE_UNTIL, CONNECTION_ALERT_SNOOZE_UNTIL_DEFAULT) + return sharedPrefs.getLong(SHARED_PREFS_CONNECTION_ALERT_SNOOZE_UNTIL, 0L) } fun setConnectionAlertSnoozeUntil(timeMillis: Long) { @@ -608,6 +618,11 @@ class Repository(private val sharedPrefs: SharedPreferences, database: Database) return connectionDetails.toMap() } + fun clearConnectionDetails() { + connectionDetails.clear() + connectionDetailsLiveData.postValue(emptyMap()) + } + fun getConnectionForceReconnectVersion(baseUrl: String): Long { return connectionForceReconnectVersions[baseUrl] ?: 0L } @@ -639,9 +654,8 @@ class Repository(private val sharedPrefs: SharedPreferences, database: Database) const val SHARED_PREFS_WEBSOCKET_RECONNECT_REMIND_TIME = "WebSocketReconnectRemindTime" const val SHARED_PREFS_UNIFIED_PUSH_BASE_URL = "UnifiedPushBaseURL" // Legacy key required for migration to DefaultBaseURL const val SHARED_PREFS_DEFAULT_BASE_URL = "DefaultBaseURL" + const val SHARED_PREFS_CONNECTION_ALERT_SECONDS = "ConnectionAlertSeconds" const val SHARED_PREFS_CONNECTION_ALERT_SNOOZE_UNTIL = "ConnectionAlertSnoozeUntil" - const val CONNECTION_ALERT_SNOOZE_UNTIL_DEFAULT = 0L - const val CONNECTION_ALERT_NEVER_SHOW = Long.MAX_VALUE const val SHARED_PREFS_LAST_TOPICS = "LastTopics" private const val LAST_TOPICS_COUNT = 3 @@ -671,6 +685,14 @@ class Repository(private val sharedPrefs: SharedPreferences, database: Database) const val INSISTENT_MAX_PRIORITY_USE_GLOBAL = -1 // Values must match values.xml const val INSISTENT_MAX_PRIORITY_ENABLED = 1 // 0 = Disabled (but not needed in code) + const val CONNECTION_ALERT_NEVER = 0L + const val CONNECTION_ALERT_FIVE_MINUTES = 5 * 60L + const val CONNECTION_ALERT_FIFTEEN_MINUTES = 15 * 60L + const val CONNECTION_ALERT_ONE_HOUR = 60 * 60L + const val CONNECTION_ALERT_THREE_HOURS = 3 * 60 * 60L + const val CONNECTION_ALERT_TWELVE_HOURS = 12 * 60 * 60L + const val CONNECTION_ALERT_DEFAULT = CONNECTION_ALERT_NEVER + const val CONNECTION_PROTOCOL_JSONHTTP = "jsonhttp" const val CONNECTION_PROTOCOL_WS = "ws" diff --git a/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt b/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt index 56d3233c..189537a0 100644 --- a/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt +++ b/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt @@ -11,6 +11,8 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.pm.ServiceInfo +import android.net.ConnectivityManager +import android.net.NetworkCapabilities import android.os.Build import android.os.IBinder import android.os.PowerManager @@ -24,8 +26,6 @@ import io.heckel.ntfy.db.Repository import io.heckel.ntfy.db.Subscription import io.heckel.ntfy.msg.ApiService import io.heckel.ntfy.msg.NotificationDispatcher -import io.heckel.ntfy.msg.NotificationService -import io.heckel.ntfy.util.PRIORITY_HIGH import io.heckel.ntfy.ui.Colors import io.heckel.ntfy.ui.MainActivity import io.heckel.ntfy.util.HttpUtil @@ -316,25 +316,40 @@ class SubscriberService : Service() { } private fun maybeShowConnectionAlert() { + val thresholdSeconds = repository.getConnectionAlertSeconds() + if (thresholdSeconds <= 0L) return + + // Don't show alert if the device has no network connectivity (e.g. airplane mode) + if (!isNetworkAvailable()) return + val now = System.currentTimeMillis() - // Check snooze / never-show-again + // Check snooze val snoozeUntil = repository.getConnectionAlertSnoozeUntil() - if (snoozeUntil == Repository.CONNECTION_ALERT_NEVER_SHOW) return if (snoozeUntil > now) return - // Check if any connection has been in error for 15+ minutes + // Check if any connection has been in error for longer than the threshold + val thresholdMillis = thresholdSeconds * 1000L val allDetails = repository.getConnectionDetails() val disconnectedUrls = allDetails.filter { (_, details) -> details.hasError() && details.firstErrorTime > 0L && - (now - details.firstErrorTime) >= CONNECTION_ALERT_THRESHOLD_MILLIS + (now - details.firstErrorTime) >= thresholdMillis }.keys if (disconnectedUrls.isNotEmpty()) { - showConnectionAlertNotification(disconnectedUrls) + val thresholdMinutes = (thresholdSeconds / 60).toInt() + showConnectionAlertNotification(disconnectedUrls, thresholdMinutes) } } + private fun isNetworkAvailable(): Boolean { + val connectivityManager = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager + val network = connectivityManager.activeNetwork ?: return false + val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false + return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) + } + private fun maybeAutoDismissConnectionAlert() { val allDetails = repository.getConnectionDetails() val anyStillDisconnected = allDetails.any { (_, details) -> @@ -345,36 +360,28 @@ class SubscriberService : Service() { } } - private fun showConnectionAlertNotification(disconnectedUrls: Set) { + private fun showConnectionAlertNotification(disconnectedUrls: Set, thresholdMinutes: Int) { val text = if (disconnectedUrls.size == 1) { - getString(R.string.connection_alert_text_one, disconnectedUrls.first(), CONNECTION_ALERT_THRESHOLD_MINUTES) + getString(R.string.connection_alert_text_one, disconnectedUrls.first(), thresholdMinutes) } else { - getString(R.string.connection_alert_text_multiple, disconnectedUrls.size, CONNECTION_ALERT_THRESHOLD_MINUTES) + getString(R.string.connection_alert_text_multiple, disconnectedUrls.size, thresholdMinutes) } - val dismissIntent = Intent(this, ConnectionAlertBroadcastReceiver::class.java).apply { - action = CONNECTION_ALERT_ACTION_DISMISS - } - val dismissPendingIntent = PendingIntent.getBroadcast(this, 0, dismissIntent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) - - val snoozeIntent = Intent(this, ConnectionAlertBroadcastReceiver::class.java).apply { - action = CONNECTION_ALERT_ACTION_SNOOZE - } - val snoozePendingIntent = PendingIntent.getBroadcast(this, 1, snoozeIntent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) - - val neverIntent = Intent(this, ConnectionAlertBroadcastReceiver::class.java).apply { - action = CONNECTION_ALERT_ACTION_NEVER - } - val neverPendingIntent = PendingIntent.getBroadcast(this, 2, neverIntent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) - val contentIntent = PendingIntent.getActivity(this, 0, Intent(this, MainActivity::class.java), PendingIntent.FLAG_IMMUTABLE) - val channelId = NotificationService(this).toChannelId(NotificationService.DEFAULT_GROUP, PRIORITY_HIGH) - val notification = NotificationCompat.Builder(this, channelId) + val snoozeIntent = PendingIntent.getBroadcast(this, 0, + Intent(this, ConnectionAlertBroadcastReceiver::class.java).apply { action = CONNECTION_ALERT_ACTION_SNOOZE }, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) + val disableIntent = PendingIntent.getBroadcast(this, 1, + Intent(this, ConnectionAlertBroadcastReceiver::class.java).apply { action = CONNECTION_ALERT_ACTION_DISABLE }, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) + + val deleteIntent = PendingIntent.getBroadcast(this, 2, + Intent(this, ConnectionAlertBroadcastReceiver::class.java).apply { action = CONNECTION_ALERT_ACTION_SNOOZE }, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) + + val notification = NotificationCompat.Builder(this, NOTIFICATION_CONNECTION_ALERT_CHANNEL_ID) .setSmallIcon(R.drawable.ic_notification) .setColor(Colors.notificationIcon(this)) .setContentTitle(getString(R.string.connection_alert_title)) @@ -383,9 +390,9 @@ class SubscriberService : Service() { .setContentIntent(contentIntent) .setAutoCancel(true) .setOnlyAlertOnce(true) - .addAction(NotificationCompat.Action.Builder(0, getString(R.string.connection_alert_action_dismiss), dismissPendingIntent).build()) - .addAction(NotificationCompat.Action.Builder(0, getString(R.string.connection_alert_action_snooze), snoozePendingIntent).build()) - .addAction(NotificationCompat.Action.Builder(0, getString(R.string.connection_alert_action_never), neverPendingIntent).build()) + .setDeleteIntent(deleteIntent) + .addAction(NotificationCompat.Action.Builder(0, getString(R.string.connection_alert_action_snooze, CONNECTION_ALERT_SNOOZE_HOURS), snoozeIntent).build()) + .addAction(NotificationCompat.Action.Builder(0, getString(R.string.connection_alert_action_disable), disableIntent).build()) .build() Log.d(TAG, "Showing connection alert notification") @@ -440,6 +447,9 @@ class SubscriberService : Service() { it } notificationManager.createNotificationChannel(channel) + val connectionAlertChannelName = getString(R.string.channel_connection_alert_name) + val connectionAlertChannel = NotificationChannel(NOTIFICATION_CONNECTION_ALERT_CHANNEL_ID, connectionAlertChannelName, NotificationManager.IMPORTANCE_DEFAULT) + notificationManager.createNotificationChannel(connectionAlertChannel) return notificationManager } @@ -500,22 +510,16 @@ class SubscriberService : Service() { Log.d(TAG, "ConnectionAlertBroadcastReceiver: action=${intent.action}") val repository = Repository.getInstance(context) val notificationManager = context.getSystemService(NOTIFICATION_SERVICE) as NotificationManager - when (intent.action) { - CONNECTION_ALERT_ACTION_DISMISS -> { - notificationManager.cancel(NOTIFICATION_CONNECTION_ALERT_ID) - } CONNECTION_ALERT_ACTION_SNOOZE -> { - repository.setConnectionAlertSnoozeUntil( - System.currentTimeMillis() + CONNECTION_ALERT_SNOOZE_DURATION_MILLIS - ) - notificationManager.cancel(NOTIFICATION_CONNECTION_ALERT_ID) + repository.setConnectionAlertSnoozeUntil(System.currentTimeMillis() + CONNECTION_ALERT_SNOOZE_DURATION_MILLIS) } - CONNECTION_ALERT_ACTION_NEVER -> { - repository.setConnectionAlertSnoozeUntil(Repository.CONNECTION_ALERT_NEVER_SHOW) - notificationManager.cancel(NOTIFICATION_CONNECTION_ALERT_ID) + CONNECTION_ALERT_ACTION_DISABLE -> { + repository.setConnectionAlertSeconds(Repository.CONNECTION_ALERT_NEVER) } + else -> return } + notificationManager.cancel(NOTIFICATION_CONNECTION_ALERT_ID) } } @@ -531,16 +535,15 @@ class SubscriberService : Service() { private const val WAKE_LOCK_TAG = "SubscriberService:lock" private const val NOTIFICATION_CHANNEL_ID = "ntfy-subscriber" + private const val NOTIFICATION_CONNECTION_ALERT_CHANNEL_ID = "ntfy-connection-alert" private const val NOTIFICATION_GROUP_ID = "io.heckel.ntfy.NOTIFICATION_GROUP_SERVICE" private const val NOTIFICATION_SERVICE_ID = 2586 private const val NOTIFICATION_RECEIVED_WAKELOCK_TIMEOUT_MILLIS = 10 * 60 * 1000L /*10 minutes*/ private const val NOTIFICATION_CONNECTION_ALERT_ID = 2587 - private const val CONNECTION_ALERT_THRESHOLD_MINUTES = 15 - private const val CONNECTION_ALERT_THRESHOLD_MILLIS = CONNECTION_ALERT_THRESHOLD_MINUTES * 60 * 1000L - private const val CONNECTION_ALERT_SNOOZE_DURATION_MILLIS = 60 * 60 * 1000L /*1 hour*/ - private const val CONNECTION_ALERT_ACTION_DISMISS = "io.heckel.ntfy.CONNECTION_ALERT_DISMISS" + private const val CONNECTION_ALERT_SNOOZE_HOURS = 8 + private const val CONNECTION_ALERT_SNOOZE_DURATION_MILLIS = CONNECTION_ALERT_SNOOZE_HOURS * 60 * 60 * 1000L private const val CONNECTION_ALERT_ACTION_SNOOZE = "io.heckel.ntfy.CONNECTION_ALERT_SNOOZE" - private const val CONNECTION_ALERT_ACTION_NEVER = "io.heckel.ntfy.CONNECTION_ALERT_NEVER" + private const val CONNECTION_ALERT_ACTION_DISABLE = "io.heckel.ntfy.CONNECTION_ALERT_DISABLE" } } diff --git a/app/src/main/java/io/heckel/ntfy/service/SubscriberServiceManager.kt b/app/src/main/java/io/heckel/ntfy/service/SubscriberServiceManager.kt index 71f51b48..8109e66d 100644 --- a/app/src/main/java/io/heckel/ntfy/service/SubscriberServiceManager.kt +++ b/app/src/main/java/io/heckel/ntfy/service/SubscriberServiceManager.kt @@ -1,7 +1,10 @@ package io.heckel.ntfy.service +import android.app.NotificationManager import android.content.Context import android.content.Intent +import android.net.ConnectivityManager +import android.net.NetworkCapabilities import androidx.core.content.ContextCompat import androidx.work.* import io.heckel.ntfy.app.Application @@ -40,8 +43,9 @@ class SubscriberServiceManager(private val context: Context) { withContext(Dispatchers.IO) { val app = context.applicationContext as Application val subscriptionIdsWithInstantStatus = app.repository.getSubscriptionIdsWithInstantStatus() + val hasNetwork = isNetworkAvailable(context) val instantSubscriptions = subscriptionIdsWithInstantStatus.toList().filter { (_, instant) -> instant }.size - if (instantSubscriptions > 0) { + if (instantSubscriptions > 0 && hasNetwork) { // We have instant subscriptions, start the service Log.d(TAG, "ServiceStartWorker: Starting foreground service (work ID: ${id})") Intent(context, SubscriberService::class.java).also { @@ -56,9 +60,14 @@ class SubscriberServiceManager(private val context: Context) { } } } else { - // No instant subscriptions, stop the service using stopService() + // No instant subscriptions (or no network), stop the service using stopService() // This avoids ForegroundServiceDidNotStartInTimeException, see #1520 Log.d(TAG, "ServiceStartWorker: Stopping service (work ID: ${id})") + if (!hasNetwork) { + app.repository.clearConnectionDetails() + val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + notificationManager.cancel(NOTIFICATION_CONNECTION_ALERT_ID) + } Intent(context, SubscriberService::class.java).also { context.stopService(it) } @@ -71,6 +80,14 @@ class SubscriberServiceManager(private val context: Context) { companion object { const val TAG = "NtfySubscriberMgr" const val WORK_NAME_ONCE = "ServiceStartWorkerOnce" + private const val NOTIFICATION_CONNECTION_ALERT_ID = 2587 // Same as SubscriberService + + private fun isNetworkAvailable(context: Context): Boolean { + val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + val network = connectivityManager.activeNetwork ?: return false + val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false + return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + } fun refresh(context: Context) { val manager = SubscriberServiceManager(context) diff --git a/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt index 251a4ba0..2ce74222 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt @@ -6,8 +6,13 @@ import android.animation.AnimatorListenerAdapter import android.app.AlarmManager import android.app.AlertDialog import android.content.ActivityNotFoundException +import android.content.Context import android.content.Intent import android.content.pm.PackageManager +import android.net.ConnectivityManager +import android.net.Network +import android.net.NetworkCapabilities +import android.net.NetworkRequest import android.os.Build import android.os.Bundle import android.provider.Settings @@ -95,6 +100,7 @@ class MainActivity : AppCompatActivity(), AddFragment.SubscribeListener, Notific private lateinit var fab: FloatingActionButton // Other stuff + private var networkCallback: ConnectivityManager.NetworkCallback? = null private var workManager: WorkManager? = null // Context-dependent private var dispatcher: NotificationDispatcher? = null // Context-dependent private var appBaseUrl: String? = null // Context-dependent @@ -342,6 +348,22 @@ class MainActivity : AppCompatActivity(), AddFragment.SubscribeListener, Notific } } + // Network state banner + showHideNoNetworkBanner() + val connectivityManager = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager + networkCallback = object : ConnectivityManager.NetworkCallback() { + override fun onAvailable(network: Network) { + runOnUiThread { showHideNoNetworkBanner() } + } + override fun onLost(network: Network) { + runOnUiThread { showHideNoNetworkBanner() } + } + } + val networkRequest = NetworkRequest.Builder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .build() + connectivityManager.registerNetworkCallback(networkRequest, networkCallback!!) + // Hide links that lead to payments, see https://github.com/binwiederhier/ntfy/issues/1463 val howToLink = findViewById(R.id.main_how_to_link) howToLink.isVisible = BuildConfig.PAYMENT_LINKS_AVAILABLE @@ -377,6 +399,7 @@ class MainActivity : AppCompatActivity(), AddFragment.SubscribeListener, Notific super.onResume() showHideNotificationMenuItems() showHideConnectionErrorMenuItem(repository.getConnectionDetails()) + showHideNoNetworkBanner() redrawList() } @@ -425,6 +448,30 @@ class MainActivity : AppCompatActivity(), AddFragment.SubscribeListener, Notific } } + private fun showHideNoNetworkBanner() { + val connectivityManager = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager + val network = connectivityManager.activeNetwork + val hasNetwork = if (network != null) { + val capabilities = connectivityManager.getNetworkCapabilities(network) + capabilities?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) == true + } else { + false + } + val banner = findViewById(R.id.main_banner_no_network) + banner.visibility = if (hasNetwork) View.GONE else View.VISIBLE + } + + override fun onDestroy() { + super.onDestroy() + try { + val connectivityManager = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager + networkCallback?.let { connectivityManager.unregisterNetworkCallback(it) } + } catch (e: Exception) { + Log.d(TAG, "Failed to unregister network callback: ${e.message}") + } + networkCallback = null + } + private fun schedulePeriodicPollWorker() { val workerVersion = repository.getPollWorkerVersion() val workPolicy = if (workerVersion == PollWorker.VERSION) { diff --git a/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt index 8ebf2f87..ba8c9dc1 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt @@ -327,6 +327,32 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere } } + // Connection alert + val connectionAlertPrefId = context?.getString(R.string.settings_notifications_connection_alert_key) ?: return + val connectionAlert: ListPreference? = findPreference(connectionAlertPrefId) + connectionAlert?.value = repository.getConnectionAlertSeconds().toString() + connectionAlert?.preferenceDataStore = object : PreferenceDataStore() { + override fun putString(key: String?, value: String?) { + val seconds = value?.toLongOrNull() ?:return + repository.setConnectionAlertSeconds(seconds) + } + override fun getString(key: String?, defValue: String?): String { + return repository.getConnectionAlertSeconds().toString() + } + } + connectionAlert?.summaryProvider = Preference.SummaryProvider { pref -> + when (pref.value.toLongOrNull() ?: repository.getConnectionAlertSeconds()) { + Repository.CONNECTION_ALERT_NEVER -> getString(R.string.settings_notifications_connection_alert_summary_never) + 30L -> "Alert after 30 seconds (testing)" + Repository.CONNECTION_ALERT_FIVE_MINUTES -> getString(R.string.settings_notifications_connection_alert_summary_five_minutes) + Repository.CONNECTION_ALERT_FIFTEEN_MINUTES -> getString(R.string.settings_notifications_connection_alert_summary_fifteen_minutes) + Repository.CONNECTION_ALERT_ONE_HOUR -> getString(R.string.settings_notifications_connection_alert_summary_one_hour) + Repository.CONNECTION_ALERT_THREE_HOURS -> getString(R.string.settings_notifications_connection_alert_summary_three_hours) + Repository.CONNECTION_ALERT_TWELVE_HOURS -> getString(R.string.settings_notifications_connection_alert_summary_twelve_hours) + else -> getString(R.string.settings_notifications_connection_alert_summary_never) // Must match default const + } + } + // Dark mode val darkModePrefId = context?.getString(R.string.settings_general_dark_mode_key) ?: return val darkMode: ListPreference? = findPreference(darkModePrefId) diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 1bf20371..282e2c7a 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -232,6 +232,22 @@ + + + app:layout_constraintTop_toBottomOf="@id/main_banner_no_network"> Връзката е загубена От най-малко %2$d минути няма връзка с %1$s От най-малко %2$d минути няма връзка със сървърите на %1$d - Отхвърляне - Отлагане с 1ч - Да не се показва повече + diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 41d8ddb6..dddbc759 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -474,7 +474,5 @@ Verbindung verloren Es konnte länger als %2$d Minuten lang keine Verbindung zu %1$s hergestellt werden Es konnte länger als %2$d Minuten lang keine Verbindung zu %1$d Servern hergestellt werden - Verwerfen - 1 Stunde schlummern - Niemals zeigen + diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 2151e87c..129c9a79 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -413,9 +413,7 @@ Conexión perdida No se pudo conectar a %1$s por más de %2$d minutos No se pudo conectar a %1$d servidores por más de %2$d minutos - Ignorar - Suspender 1h - Nunca mostrar + Su búsqueda no tuvo ningún resultado Buscar notificaciones Buscar en notificaciones diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 0d9b6459..c18876c5 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -464,7 +464,5 @@ Ühendus on katkenud Ühendus %1$s teenusega toimib vaid %2$d minuti(t) Ühendus %1$d serveriga toimib vaid %2$d minuti(t) - Loobu - Tukasta 1t - Ära näita iialgi + diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index c4b04a7e..22a1fda1 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -474,7 +474,5 @@ Connexion perdue Impossible de se connecter à %1$s depuis plus de %2$d minutes Impossible de se connecter à %1$d serveurs depuis plus de %2$d minutes - Ignorer - Sourdine 1h - Ne jamais montrer + diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index aea03527..d0ecf643 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -474,7 +474,4 @@ 接続が切断されました %2$d 分以上 %1$s に接続できませんでした %1$d 個のサーバーに %2$d 分以上接続できませんでした - 了解 - 1時間後に再通知 - 再表示しない diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index a566e76d..2ee259f6 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -378,9 +378,7 @@ Verbinding verbroken Kan geen verbinding maken met %1$s voor meer dan %2$d minuten Kan geen verbinding maken met %1$d servers voor meer dan %2$d minuten - Afwijzen - Sluimer voor 1 uur - Nooit laten zien + Publieer naar %1$s Uploaden: %1$s (%2$s / %3$s) Upload geannuleerd diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index bf4ef4a6..00489ea8 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -474,7 +474,5 @@ 连接丢失 已无法连接到 %1$s 超过 %2$d 分钟 已无法连接到 %1$d 台服务器超过 %2$d 分钟 - 关闭 - 延后 1 小时 - 永不显示 + diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 77191ab5..920d6956 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -474,7 +474,5 @@ 連線已中斷 超過 %2$d 分鐘無法連線至 %1$s 超過 %2$d 分鐘無法連線至 %1$d 個伺服器 - 關閉 - 延後 1 小時 - 永不顯示 + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 986a33e4..f2eb6bce 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -29,6 +29,7 @@ Default Subscription Service + Connection alerts Listening for incoming notifications Subscribed to instant delivery topics Subscribed to one instant delivery topic @@ -49,11 +50,10 @@ Connection lost - Unable to connect to %1$s for more than %2$d minutes - Unable to connect to %1$d servers for more than %2$d minutes - Dismiss - Snooze 1h - Never show + Unable to connect to %1$s for more than %2$d minutes. Check your network connection. + Unable to connect to %1$d servers for more than %2$d minutes. Check your network connection. + Snooze %1$dh + Disable alerts %1$d notification(s) received @@ -115,6 +115,7 @@ Ask later Dismiss Grant now + No network Subscribe to topic @@ -370,6 +371,19 @@ After one week After one month After 3 months + Alert when connection is lost + Never alert when connection is lost + Alert after 5 minutes without connection + Alert after 15 minutes without connection + Alert after 1 hour without connection + Alert after 3 hours without connection + Alert after 12 hours without connection + Never + After 5 minutes + After 15 minutes + After 1 hour + After 3 hours + After 12 hours Keep alerting for highest priority Max priority notifications continuously alert until dismissed Max priority notifications only alert once diff --git a/app/src/main/res/values/values.xml b/app/src/main/res/values/values.xml index 0b45baf5..bc1e156e 100644 --- a/app/src/main/res/values/values.xml +++ b/app/src/main/res/values/values.xml @@ -47,6 +47,7 @@ SubscriptionMinPriority SubscriptionAutoDelete SubscriptionInsistentMaxPriority + ConnectionAlert SubscriptionAppearance SubscriptionIconSet SubscriptionIconRemove @@ -167,6 +168,24 @@ 1 0 + + @string/settings_notifications_connection_alert_never + After 30 seconds (testing) + @string/settings_notifications_connection_alert_five_minutes + @string/settings_notifications_connection_alert_fifteen_minutes + @string/settings_notifications_connection_alert_one_hour + @string/settings_notifications_connection_alert_three_hours + @string/settings_notifications_connection_alert_twelve_hours + + + 0 + 30 + 300 + 900 + 3600 + 10800 + 43200 + @string/settings_advanced_connection_protocol_entry_jsonhttp @string/settings_advanced_connection_protocol_entry_ws diff --git a/app/src/main/res/xml/main_preferences.xml b/app/src/main/res/xml/main_preferences.xml index 1ef47d3e..dd0dd085 100644 --- a/app/src/main/res/xml/main_preferences.xml +++ b/app/src/main/res/xml/main_preferences.xml @@ -29,6 +29,12 @@ app:key="@string/settings_notifications_insistent_max_priority_key" app:title="@string/settings_notifications_insistent_max_priority_title" app:defaultValue="false"/> + Date: Wed, 1 Apr 2026 21:56:07 -0400 Subject: [PATCH 16/20] WIP Connection lost alert v2 --- app/src/main/AndroidManifest.xml | 3 +- .../java/io/heckel/ntfy/app/Application.kt | 7 +---- .../main/java/io/heckel/ntfy/db/Repository.kt | 1 + .../heckel/ntfy/service/SubscriberService.kt | 31 +++++++++---------- .../ntfy/service/SubscriberServiceManager.kt | 9 +----- .../java/io/heckel/ntfy/ui/MainActivity.kt | 18 ++--------- .../io/heckel/ntfy/ui/SettingsActivity.kt | 16 +++++----- app/src/main/java/io/heckel/ntfy/util/Util.kt | 8 +++++ .../res/drawable/ic_wifi_off_gray_24dp.xml | 9 ++++++ app/src/main/res/layout/activity_main.xml | 22 ++++++++----- app/src/main/res/values-bg/strings.xml | 3 +- app/src/main/res/values-de/strings.xml | 3 +- app/src/main/res/values-es/strings.xml | 3 +- app/src/main/res/values-et/strings.xml | 3 +- app/src/main/res/values-fr/strings.xml | 3 +- app/src/main/res/values-ja/strings.xml | 2 ++ app/src/main/res/values-nl/strings.xml | 3 +- app/src/main/res/values-pt-rBR/strings.xml | 3 +- app/src/main/res/values-zh-rCN/strings.xml | 3 +- app/src/main/res/values-zh-rTW/strings.xml | 3 +- app/src/main/res/values/strings.xml | 31 ++++++++++--------- app/src/main/res/values/values.xml | 18 +++++------ app/src/main/res/xml/main_preferences.xml | 12 +++---- 23 files changed, 111 insertions(+), 103 deletions(-) create mode 100644 app/src/main/res/drawable/ic_wifi_off_gray_24dp.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f2df392b..28c03a77 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -138,8 +138,9 @@ android:enabled="true" android:exported="false"> + - + diff --git a/app/src/main/java/io/heckel/ntfy/app/Application.kt b/app/src/main/java/io/heckel/ntfy/app/Application.kt index bc06f046..da9219ea 100644 --- a/app/src/main/java/io/heckel/ntfy/app/Application.kt +++ b/app/src/main/java/io/heckel/ntfy/app/Application.kt @@ -3,8 +3,6 @@ package io.heckel.ntfy.app import android.app.Application import android.net.ConnectivityManager import android.net.Network -import android.net.NetworkCapabilities -import android.net.NetworkRequest import com.google.android.material.color.DynamicColors import io.heckel.ntfy.db.Repository import io.heckel.ntfy.service.SubscriberServiceManager @@ -34,10 +32,7 @@ class Application : Application() { private fun registerNetworkCallback() { val connectivityManager = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager - val networkRequest = NetworkRequest.Builder() - .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) - .build() - connectivityManager.registerNetworkCallback(networkRequest, object : ConnectivityManager.NetworkCallback() { + connectivityManager.registerDefaultNetworkCallback(object : ConnectivityManager.NetworkCallback() { override fun onAvailable(network: Network) { SubscriberServiceManager.refresh(this@Application) } diff --git a/app/src/main/java/io/heckel/ntfy/db/Repository.kt b/app/src/main/java/io/heckel/ntfy/db/Repository.kt index fc3dd561..6d024ecd 100644 --- a/app/src/main/java/io/heckel/ntfy/db/Repository.kt +++ b/app/src/main/java/io/heckel/ntfy/db/Repository.kt @@ -447,6 +447,7 @@ class Repository(private val sharedPrefs: SharedPreferences, database: Database) fun setConnectionAlertSeconds(seconds: Long) { sharedPrefs.edit { putLong(SHARED_PREFS_CONNECTION_ALERT_SECONDS, seconds) + putLong(SHARED_PREFS_CONNECTION_ALERT_SNOOZE_UNTIL, 0L) } } diff --git a/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt b/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt index 189537a0..94a37d76 100644 --- a/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt +++ b/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt @@ -11,8 +11,7 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.pm.ServiceInfo -import android.net.ConnectivityManager -import android.net.NetworkCapabilities +import io.heckel.ntfy.util.isNetworkAvailable import android.os.Build import android.os.IBinder import android.os.PowerManager @@ -30,6 +29,7 @@ import io.heckel.ntfy.ui.Colors import io.heckel.ntfy.ui.MainActivity import io.heckel.ntfy.util.HttpUtil import io.heckel.ntfy.util.Log +import io.heckel.ntfy.util.shortUrl import io.heckel.ntfy.util.topicUrl import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope @@ -320,7 +320,7 @@ class SubscriberService : Service() { if (thresholdSeconds <= 0L) return // Don't show alert if the device has no network connectivity (e.g. airplane mode) - if (!isNetworkAvailable()) return + if (!isNetworkAvailable(this)) return val now = System.currentTimeMillis() @@ -342,14 +342,6 @@ class SubscriberService : Service() { } } - private fun isNetworkAvailable(): Boolean { - val connectivityManager = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager - val network = connectivityManager.activeNetwork ?: return false - val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false - return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) - && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) - } - private fun maybeAutoDismissConnectionAlert() { val allDetails = repository.getConnectionDetails() val anyStillDisconnected = allDetails.any { (_, details) -> @@ -362,7 +354,7 @@ class SubscriberService : Service() { private fun showConnectionAlertNotification(disconnectedUrls: Set, thresholdMinutes: Int) { val text = if (disconnectedUrls.size == 1) { - getString(R.string.connection_alert_text_one, disconnectedUrls.first(), thresholdMinutes) + getString(R.string.connection_alert_text_one, shortUrl(disconnectedUrls.first()), thresholdMinutes) } else { getString(R.string.connection_alert_text_multiple, disconnectedUrls.size, thresholdMinutes) } @@ -374,11 +366,11 @@ class SubscriberService : Service() { Intent(this, ConnectionAlertBroadcastReceiver::class.java).apply { action = CONNECTION_ALERT_ACTION_SNOOZE }, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) val disableIntent = PendingIntent.getBroadcast(this, 1, - Intent(this, ConnectionAlertBroadcastReceiver::class.java).apply { action = CONNECTION_ALERT_ACTION_DISABLE }, + Intent(this, ConnectionAlertBroadcastReceiver::class.java).apply { action = CONNECTION_ALERT_ACTION_NEVER }, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) val deleteIntent = PendingIntent.getBroadcast(this, 2, - Intent(this, ConnectionAlertBroadcastReceiver::class.java).apply { action = CONNECTION_ALERT_ACTION_SNOOZE }, + Intent(this, ConnectionAlertBroadcastReceiver::class.java).apply { action = CONNECTION_ALERT_ACTION_DISMISS }, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) val notification = NotificationCompat.Builder(this, NOTIFICATION_CONNECTION_ALERT_CHANNEL_ID) @@ -392,7 +384,7 @@ class SubscriberService : Service() { .setOnlyAlertOnce(true) .setDeleteIntent(deleteIntent) .addAction(NotificationCompat.Action.Builder(0, getString(R.string.connection_alert_action_snooze, CONNECTION_ALERT_SNOOZE_HOURS), snoozeIntent).build()) - .addAction(NotificationCompat.Action.Builder(0, getString(R.string.connection_alert_action_disable), disableIntent).build()) + .addAction(NotificationCompat.Action.Builder(0, getString(R.string.connection_alert_action_never), disableIntent).build()) .build() Log.d(TAG, "Showing connection alert notification") @@ -511,10 +503,13 @@ class SubscriberService : Service() { val repository = Repository.getInstance(context) val notificationManager = context.getSystemService(NOTIFICATION_SERVICE) as NotificationManager when (intent.action) { + CONNECTION_ALERT_ACTION_DISMISS -> { + repository.setConnectionAlertSnoozeUntil(System.currentTimeMillis() + CONNECTION_ALERT_DISMISS_SNOOZE_MILLIS) + } CONNECTION_ALERT_ACTION_SNOOZE -> { repository.setConnectionAlertSnoozeUntil(System.currentTimeMillis() + CONNECTION_ALERT_SNOOZE_DURATION_MILLIS) } - CONNECTION_ALERT_ACTION_DISABLE -> { + CONNECTION_ALERT_ACTION_NEVER -> { repository.setConnectionAlertSeconds(Repository.CONNECTION_ALERT_NEVER) } else -> return @@ -543,7 +538,9 @@ class SubscriberService : Service() { private const val NOTIFICATION_CONNECTION_ALERT_ID = 2587 private const val CONNECTION_ALERT_SNOOZE_HOURS = 8 private const val CONNECTION_ALERT_SNOOZE_DURATION_MILLIS = CONNECTION_ALERT_SNOOZE_HOURS * 60 * 60 * 1000L + private const val CONNECTION_ALERT_DISMISS_SNOOZE_MILLIS = 1 * 60 * 60 * 1000L /*1 hour*/ + private const val CONNECTION_ALERT_ACTION_DISMISS = "io.heckel.ntfy.CONNECTION_ALERT_DISMISS" private const val CONNECTION_ALERT_ACTION_SNOOZE = "io.heckel.ntfy.CONNECTION_ALERT_SNOOZE" - private const val CONNECTION_ALERT_ACTION_DISABLE = "io.heckel.ntfy.CONNECTION_ALERT_DISABLE" + private const val CONNECTION_ALERT_ACTION_NEVER = "io.heckel.ntfy.CONNECTION_ALERT_NEVER" } } diff --git a/app/src/main/java/io/heckel/ntfy/service/SubscriberServiceManager.kt b/app/src/main/java/io/heckel/ntfy/service/SubscriberServiceManager.kt index 8109e66d..a8fe62bc 100644 --- a/app/src/main/java/io/heckel/ntfy/service/SubscriberServiceManager.kt +++ b/app/src/main/java/io/heckel/ntfy/service/SubscriberServiceManager.kt @@ -3,8 +3,7 @@ package io.heckel.ntfy.service import android.app.NotificationManager import android.content.Context import android.content.Intent -import android.net.ConnectivityManager -import android.net.NetworkCapabilities +import io.heckel.ntfy.util.isNetworkAvailable import androidx.core.content.ContextCompat import androidx.work.* import io.heckel.ntfy.app.Application @@ -82,12 +81,6 @@ class SubscriberServiceManager(private val context: Context) { const val WORK_NAME_ONCE = "ServiceStartWorkerOnce" private const val NOTIFICATION_CONNECTION_ALERT_ID = 2587 // Same as SubscriberService - private fun isNetworkAvailable(context: Context): Boolean { - val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager - val network = connectivityManager.activeNetwork ?: return false - val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false - return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) - } fun refresh(context: Context) { val manager = SubscriberServiceManager(context) diff --git a/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt index 2ce74222..6083ae70 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt @@ -11,8 +11,7 @@ import android.content.Intent import android.content.pm.PackageManager import android.net.ConnectivityManager import android.net.Network -import android.net.NetworkCapabilities -import android.net.NetworkRequest +import io.heckel.ntfy.util.isNetworkAvailable import android.os.Build import android.os.Bundle import android.provider.Settings @@ -359,10 +358,7 @@ class MainActivity : AppCompatActivity(), AddFragment.SubscribeListener, Notific runOnUiThread { showHideNoNetworkBanner() } } } - val networkRequest = NetworkRequest.Builder() - .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) - .build() - connectivityManager.registerNetworkCallback(networkRequest, networkCallback!!) + connectivityManager.registerDefaultNetworkCallback(networkCallback!!) // Hide links that lead to payments, see https://github.com/binwiederhier/ntfy/issues/1463 val howToLink = findViewById(R.id.main_how_to_link) @@ -449,16 +445,8 @@ class MainActivity : AppCompatActivity(), AddFragment.SubscribeListener, Notific } private fun showHideNoNetworkBanner() { - val connectivityManager = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager - val network = connectivityManager.activeNetwork - val hasNetwork = if (network != null) { - val capabilities = connectivityManager.getNetworkCapabilities(network) - capabilities?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) == true - } else { - false - } val banner = findViewById(R.id.main_banner_no_network) - banner.visibility = if (hasNetwork) View.GONE else View.VISIBLE + banner.visibility = if (isNetworkAvailable(this)) View.GONE else View.VISIBLE } override fun onDestroy() { diff --git a/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt index ba8c9dc1..3ba01e0d 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt @@ -328,7 +328,7 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere } // Connection alert - val connectionAlertPrefId = context?.getString(R.string.settings_notifications_connection_alert_key) ?: return + val connectionAlertPrefId = context?.getString(R.string.settings_advanced_connection_alert_key) ?: return val connectionAlert: ListPreference? = findPreference(connectionAlertPrefId) connectionAlert?.value = repository.getConnectionAlertSeconds().toString() connectionAlert?.preferenceDataStore = object : PreferenceDataStore() { @@ -342,14 +342,14 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere } connectionAlert?.summaryProvider = Preference.SummaryProvider { pref -> when (pref.value.toLongOrNull() ?: repository.getConnectionAlertSeconds()) { - Repository.CONNECTION_ALERT_NEVER -> getString(R.string.settings_notifications_connection_alert_summary_never) + Repository.CONNECTION_ALERT_NEVER -> getString(R.string.settings_advanced_connection_alert_summary_never) 30L -> "Alert after 30 seconds (testing)" - Repository.CONNECTION_ALERT_FIVE_MINUTES -> getString(R.string.settings_notifications_connection_alert_summary_five_minutes) - Repository.CONNECTION_ALERT_FIFTEEN_MINUTES -> getString(R.string.settings_notifications_connection_alert_summary_fifteen_minutes) - Repository.CONNECTION_ALERT_ONE_HOUR -> getString(R.string.settings_notifications_connection_alert_summary_one_hour) - Repository.CONNECTION_ALERT_THREE_HOURS -> getString(R.string.settings_notifications_connection_alert_summary_three_hours) - Repository.CONNECTION_ALERT_TWELVE_HOURS -> getString(R.string.settings_notifications_connection_alert_summary_twelve_hours) - else -> getString(R.string.settings_notifications_connection_alert_summary_never) // Must match default const + Repository.CONNECTION_ALERT_FIVE_MINUTES -> getString(R.string.settings_advanced_connection_alert_summary_five_minutes) + Repository.CONNECTION_ALERT_FIFTEEN_MINUTES -> getString(R.string.settings_advanced_connection_alert_summary_fifteen_minutes) + Repository.CONNECTION_ALERT_ONE_HOUR -> getString(R.string.settings_advanced_connection_alert_summary_one_hour) + Repository.CONNECTION_ALERT_THREE_HOURS -> getString(R.string.settings_advanced_connection_alert_summary_three_hours) + Repository.CONNECTION_ALERT_TWELVE_HOURS -> getString(R.string.settings_advanced_connection_alert_summary_twelve_hours) + else -> getString(R.string.settings_advanced_connection_alert_summary_never) // Must match default const } } diff --git a/app/src/main/java/io/heckel/ntfy/util/Util.kt b/app/src/main/java/io/heckel/ntfy/util/Util.kt index e472fcef..7ef04d84 100644 --- a/app/src/main/java/io/heckel/ntfy/util/Util.kt +++ b/app/src/main/java/io/heckel/ntfy/util/Util.kt @@ -10,6 +10,7 @@ import android.content.res.Resources import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.drawable.RippleDrawable +import android.net.ConnectivityManager import android.net.Uri import android.os.Build import android.os.PowerManager @@ -53,6 +54,13 @@ import kotlin.math.abs import kotlin.math.absoluteValue import androidx.core.net.toUri +// We check for any active network, not specifically for internet connectivity, +// because the ntfy server may be on a LAN without internet access. +fun isNetworkAvailable(context: Context): Boolean { + val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + return connectivityManager.activeNetwork != null +} + fun topicUrl(baseUrl: String, topic: String) = "${baseUrl}/${topic}" fun topicUrlUp(baseUrl: String, topic: String) = "${baseUrl}/${topic}?up=1" // UnifiedPush fun topicUrlJson(baseUrl: String, topic: String, since: String) = "${topicUrl(baseUrl, topic)}/json?since=$since" diff --git a/app/src/main/res/drawable/ic_wifi_off_gray_24dp.xml b/app/src/main/res/drawable/ic_wifi_off_gray_24dp.xml new file mode 100644 index 00000000..9dbc7e1d --- /dev/null +++ b/app/src/main/res/drawable/ic_wifi_off_gray_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 282e2c7a..1642bb78 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -232,21 +232,27 @@ - + app:layout_constraintTop_toBottomOf="@id/main_banner_websocket_reconnect"> + + Връзката е загубена От най-малко %2$d минути няма връзка с %1$s От най-малко %2$d минути няма връзка със сървърите на %1$d - + Отлагане с %1$dч + Да не се показва повече diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index dddbc759..71049eed 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -474,5 +474,6 @@ Verbindung verloren Es konnte länger als %2$d Minuten lang keine Verbindung zu %1$s hergestellt werden Es konnte länger als %2$d Minuten lang keine Verbindung zu %1$d Servern hergestellt werden - + %1$d Std. schlummern + Niemals zeigen diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 78eafc6c..4961a23e 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -413,7 +413,8 @@ Conexión perdida No se pudo conectar a %1$s por más de %2$d minutos No se pudo conectar a %1$d servidores por más de %2$d minutos - + Suspender %1$dh + Nunca mostrar Su búsqueda no tuvo ningún resultado Buscar notificaciones Buscar en notificaciones diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index c18876c5..c0639a4a 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -464,5 +464,6 @@ Ühendus on katkenud Ühendus %1$s teenusega toimib vaid %2$d minuti(t) Ühendus %1$d serveriga toimib vaid %2$d minuti(t) - + Tukasta %1$dt + Ära näita iialgi diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 22a1fda1..ac32bd0f 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -474,5 +474,6 @@ Connexion perdue Impossible de se connecter à %1$s depuis plus de %2$d minutes Impossible de se connecter à %1$d serveurs depuis plus de %2$d minutes - + Sourdine %1$dh + Ne jamais montrer diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index d0ecf643..a1c4aeae 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -474,4 +474,6 @@ 接続が切断されました %2$d 分以上 %1$s に接続できませんでした %1$d 個のサーバーに %2$d 分以上接続できませんでした + %1$d時間後に再通知 + 再表示しない diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 2ee259f6..eb1ca62e 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -378,7 +378,8 @@ Verbinding verbroken Kan geen verbinding maken met %1$s voor meer dan %2$d minuten Kan geen verbinding maken met %1$d servers voor meer dan %2$d minuten - + Sluimer voor %1$d uur + Nooit laten zien Publieer naar %1$s Uploaden: %1$s (%2$s / %3$s) Upload geannuleerd diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 3b72fca0..f8bc7a9d 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -356,8 +356,7 @@ Notificação de alerta de conexão Não foi possível conectar à %1$s por mais de %2$d minutos Não foi possível conectar aos servidores %1$d por mais de %2$d minutos - Dispensar - Adiar 1h + Adiar %1$dh Nunca mostrar Erro de conexão Certificado do servidor não confiável diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 00489ea8..166976ae 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -474,5 +474,6 @@ 连接丢失 已无法连接到 %1$s 超过 %2$d 分钟 已无法连接到 %1$d 台服务器超过 %2$d 分钟 - + 延后 %1$d 小时 + 永不显示 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 920d6956..a5553469 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -474,5 +474,6 @@ 連線已中斷 超過 %2$d 分鐘無法連線至 %1$s 超過 %2$d 分鐘無法連線至 %1$d 個伺服器 - + 延後 %1$d 小時 + 永不顯示 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f2eb6bce..3e7fb3c2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -52,8 +52,9 @@ Connection lost Unable to connect to %1$s for more than %2$d minutes. Check your network connection. Unable to connect to %1$d servers for more than %2$d minutes. Check your network connection. + Snooze %1$dh - Disable alerts + Never show %1$d notification(s) received @@ -115,7 +116,7 @@ Ask later Dismiss Grant now - No network + You\'re offline Subscribe to topic @@ -371,19 +372,19 @@ After one week After one month After 3 months - Alert when connection is lost - Never alert when connection is lost - Alert after 5 minutes without connection - Alert after 15 minutes without connection - Alert after 1 hour without connection - Alert after 3 hours without connection - Alert after 12 hours without connection - Never - After 5 minutes - After 15 minutes - After 1 hour - After 3 hours - After 12 hours + Connection lost alert + Never alert when connection is lost + Alert after 5 minutes without connection + Alert after 15 minutes without connection + Alert after 1 hour without connection + Alert after 3 hours without connection + Alert after 12 hours without connection + Never + After 5 minutes + After 15 minutes + After 1 hour + After 3 hours + After 12 hours Keep alerting for highest priority Max priority notifications continuously alert until dismissed Max priority notifications only alert once diff --git a/app/src/main/res/values/values.xml b/app/src/main/res/values/values.xml index bc1e156e..19c732b1 100644 --- a/app/src/main/res/values/values.xml +++ b/app/src/main/res/values/values.xml @@ -47,7 +47,7 @@ SubscriptionMinPriority SubscriptionAutoDelete SubscriptionInsistentMaxPriority - ConnectionAlert + ConnectionAlert SubscriptionAppearance SubscriptionIconSet SubscriptionIconRemove @@ -168,16 +168,16 @@ 1 0 - - @string/settings_notifications_connection_alert_never + + @string/settings_advanced_connection_alert_never After 30 seconds (testing) - @string/settings_notifications_connection_alert_five_minutes - @string/settings_notifications_connection_alert_fifteen_minutes - @string/settings_notifications_connection_alert_one_hour - @string/settings_notifications_connection_alert_three_hours - @string/settings_notifications_connection_alert_twelve_hours + @string/settings_advanced_connection_alert_five_minutes + @string/settings_advanced_connection_alert_fifteen_minutes + @string/settings_advanced_connection_alert_one_hour + @string/settings_advanced_connection_alert_three_hours + @string/settings_advanced_connection_alert_twelve_hours - + 0 30 300 diff --git a/app/src/main/res/xml/main_preferences.xml b/app/src/main/res/xml/main_preferences.xml index dd0dd085..c738b6c3 100644 --- a/app/src/main/res/xml/main_preferences.xml +++ b/app/src/main/res/xml/main_preferences.xml @@ -29,12 +29,6 @@ app:key="@string/settings_notifications_insistent_max_priority_key" app:title="@string/settings_notifications_insistent_max_priority_title" app:defaultValue="false"/> - + Date: Fri, 3 Apr 2026 15:17:47 -0400 Subject: [PATCH 17/20] Manual review --- .../main/java/io/heckel/ntfy/db/Repository.kt | 36 ++++++++++--------- .../heckel/ntfy/service/SubscriberService.kt | 17 +++++---- .../ntfy/service/SubscriberServiceManager.kt | 6 ++-- .../java/io/heckel/ntfy/ui/MainActivity.kt | 2 +- .../io/heckel/ntfy/ui/SettingsActivity.kt | 13 +++---- app/src/main/java/io/heckel/ntfy/util/Util.kt | 14 ++++---- app/src/main/res/values/strings.xml | 26 +++++++------- 7 files changed, 57 insertions(+), 57 deletions(-) diff --git a/app/src/main/java/io/heckel/ntfy/db/Repository.kt b/app/src/main/java/io/heckel/ntfy/db/Repository.kt index 6d024ecd..4333180d 100644 --- a/app/src/main/java/io/heckel/ntfy/db/Repository.kt +++ b/app/src/main/java/io/heckel/ntfy/db/Repository.kt @@ -447,17 +447,17 @@ class Repository(private val sharedPrefs: SharedPreferences, database: Database) fun setConnectionAlertSeconds(seconds: Long) { sharedPrefs.edit { putLong(SHARED_PREFS_CONNECTION_ALERT_SECONDS, seconds) - putLong(SHARED_PREFS_CONNECTION_ALERT_SNOOZE_UNTIL, 0L) + putLong(SHARED_PREFS_CONNECTION_ALERT_SNOOZE_UNTIL_TIME, 0L) } } - fun getConnectionAlertSnoozeUntil(): Long { - return sharedPrefs.getLong(SHARED_PREFS_CONNECTION_ALERT_SNOOZE_UNTIL, 0L) + fun getConnectionAlertSnoozeUntilTime(): Long { + return sharedPrefs.getLong(SHARED_PREFS_CONNECTION_ALERT_SNOOZE_UNTIL_TIME, 0L) } - fun setConnectionAlertSnoozeUntil(timeMillis: Long) { + fun setConnectionAlertSnoozeUntilTime(timeMillis: Long) { sharedPrefs.edit { - putLong(SHARED_PREFS_CONNECTION_ALERT_SNOOZE_UNTIL, timeMillis) + putLong(SHARED_PREFS_CONNECTION_ALERT_SNOOZE_UNTIL_TIME, timeMillis) } } @@ -650,13 +650,13 @@ class Repository(private val sharedPrefs: SharedPreferences, database: Database) const val SHARED_PREFS_INSISTENT_MAX_PRIORITY_ENABLED = "InsistentMaxPriority" const val SHARED_PREFS_RECORD_LOGS_ENABLED = "RecordLogs" const val SHARED_PREFS_MESSAGE_BAR_ENABLED = "MessageBarEnabled" - const val SHARED_PREFS_BATTERY_OPTIMIZATIONS_REMIND_TIME = "BatteryOptimizationsRemindTime" - const val SHARED_PREFS_WEBSOCKET_REMIND_TIME = "JsonStreamRemindTime" // "Use WebSocket" banner (used to be JSON stream deprecation banner) - const val SHARED_PREFS_WEBSOCKET_RECONNECT_REMIND_TIME = "WebSocketReconnectRemindTime" + const val SHARED_PREFS_BATTERY_OPTIMIZATIONS_REMIND_TIME = "BatteryOptimizationsRemindTime" // Timestamp as millis + const val SHARED_PREFS_WEBSOCKET_REMIND_TIME = "JsonStreamRemindTime" // "Use WebSocket" banner (used to be JSON stream deprecation banner), timestamp as millis + const val SHARED_PREFS_WEBSOCKET_RECONNECT_REMIND_TIME = "WebSocketReconnectRemindTime" // Timestamp as millis const val SHARED_PREFS_UNIFIED_PUSH_BASE_URL = "UnifiedPushBaseURL" // Legacy key required for migration to DefaultBaseURL const val SHARED_PREFS_DEFAULT_BASE_URL = "DefaultBaseURL" const val SHARED_PREFS_CONNECTION_ALERT_SECONDS = "ConnectionAlertSeconds" - const val SHARED_PREFS_CONNECTION_ALERT_SNOOZE_UNTIL = "ConnectionAlertSnoozeUntil" + const val SHARED_PREFS_CONNECTION_ALERT_SNOOZE_UNTIL_TIME = "ConnectionAlertSnoozeUntilTime" // Timestamp in millis const val SHARED_PREFS_LAST_TOPICS = "LastTopics" private const val LAST_TOPICS_COUNT = 3 @@ -668,12 +668,14 @@ class Repository(private val sharedPrefs: SharedPreferences, database: Database) const val MUTED_UNTIL_FOREVER = 1L const val MUTED_UNTIL_TOMORROW = 2L - private const val ONE_MB = 1024 * 1024L + private const val ONE_MB_BYTES = 1024 * 1024L const val AUTO_DOWNLOAD_NEVER = 0L // Values must match values.xml const val AUTO_DOWNLOAD_ALWAYS = 1L - const val AUTO_DOWNLOAD_DEFAULT = ONE_MB + const val AUTO_DOWNLOAD_DEFAULT = ONE_MB_BYTES + + private const val ONE_HOUR_SECONDS = 60 * 60L + private const val ONE_DAY_SECONDS = 24 * ONE_HOUR_SECONDS - private const val ONE_DAY_SECONDS = 24 * 60 * 60L const val AUTO_DELETE_USE_GLOBAL = -1L // Values must match values.xml const val AUTO_DELETE_NEVER = 0L const val AUTO_DELETE_ONE_DAY_SECONDS = ONE_DAY_SECONDS @@ -687,11 +689,11 @@ class Repository(private val sharedPrefs: SharedPreferences, database: Database) const val INSISTENT_MAX_PRIORITY_ENABLED = 1 // 0 = Disabled (but not needed in code) const val CONNECTION_ALERT_NEVER = 0L - const val CONNECTION_ALERT_FIVE_MINUTES = 5 * 60L - const val CONNECTION_ALERT_FIFTEEN_MINUTES = 15 * 60L - const val CONNECTION_ALERT_ONE_HOUR = 60 * 60L - const val CONNECTION_ALERT_THREE_HOURS = 3 * 60 * 60L - const val CONNECTION_ALERT_TWELVE_HOURS = 12 * 60 * 60L + const val CONNECTION_ALERT_FIVE_MINUTES_SECONDS = 5 * 60L + const val CONNECTION_ALERT_FIFTEEN_MINUTES_SECONDS = 15 * 60L + const val CONNECTION_ALERT_ONE_HOUR_SECONDS = ONE_HOUR_SECONDS + const val CONNECTION_ALERT_THREE_HOURS_SECONDS = 3 * ONE_HOUR_SECONDS + const val CONNECTION_ALERT_TWELVE_HOURS_SECONDS = 12 * ONE_HOUR_SECONDS const val CONNECTION_ALERT_DEFAULT = CONNECTION_ALERT_NEVER const val CONNECTION_PROTOCOL_JSONHTTP = "jsonhttp" diff --git a/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt b/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt index 94a37d76..0377a177 100644 --- a/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt +++ b/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt @@ -11,7 +11,6 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.pm.ServiceInfo -import io.heckel.ntfy.util.isNetworkAvailable import android.os.Build import android.os.IBinder import android.os.PowerManager @@ -29,6 +28,7 @@ import io.heckel.ntfy.ui.Colors import io.heckel.ntfy.ui.MainActivity import io.heckel.ntfy.util.HttpUtil import io.heckel.ntfy.util.Log +import io.heckel.ntfy.util.isNetworkAvailable import io.heckel.ntfy.util.shortUrl import io.heckel.ntfy.util.topicUrl import kotlinx.coroutines.Dispatchers @@ -325,7 +325,7 @@ class SubscriberService : Service() { val now = System.currentTimeMillis() // Check snooze - val snoozeUntil = repository.getConnectionAlertSnoozeUntil() + val snoozeUntil = repository.getConnectionAlertSnoozeUntilTime() if (snoozeUntil > now) return // Check if any connection has been in error for longer than the threshold @@ -504,10 +504,10 @@ class SubscriberService : Service() { val notificationManager = context.getSystemService(NOTIFICATION_SERVICE) as NotificationManager when (intent.action) { CONNECTION_ALERT_ACTION_DISMISS -> { - repository.setConnectionAlertSnoozeUntil(System.currentTimeMillis() + CONNECTION_ALERT_DISMISS_SNOOZE_MILLIS) + repository.setConnectionAlertSnoozeUntilTime(System.currentTimeMillis() + CONNECTION_ALERT_DISMISS_MILLIS) } CONNECTION_ALERT_ACTION_SNOOZE -> { - repository.setConnectionAlertSnoozeUntil(System.currentTimeMillis() + CONNECTION_ALERT_SNOOZE_DURATION_MILLIS) + repository.setConnectionAlertSnoozeUntilTime(System.currentTimeMillis() + CONNECTION_ALERT_SNOOZE_MILLIS) } CONNECTION_ALERT_ACTION_NEVER -> { repository.setConnectionAlertSeconds(Repository.CONNECTION_ALERT_NEVER) @@ -528,6 +528,9 @@ class SubscriberService : Service() { const val SERVICE_START_WORKER_VERSION = BuildConfig.VERSION_CODE const val SERVICE_START_WORKER_WORK_NAME_PERIODIC = "NtfyAutoRestartWorkerPeriodic" // Do not change! + private const val ONE_HOUR_SECONDS = 60 * 60L + private const val ONE_HOUR_MILLIS = ONE_HOUR_SECONDS * 1000L + private const val WAKE_LOCK_TAG = "SubscriberService:lock" private const val NOTIFICATION_CHANNEL_ID = "ntfy-subscriber" private const val NOTIFICATION_CONNECTION_ALERT_CHANNEL_ID = "ntfy-connection-alert" @@ -535,10 +538,10 @@ class SubscriberService : Service() { private const val NOTIFICATION_SERVICE_ID = 2586 private const val NOTIFICATION_RECEIVED_WAKELOCK_TIMEOUT_MILLIS = 10 * 60 * 1000L /*10 minutes*/ - private const val NOTIFICATION_CONNECTION_ALERT_ID = 2587 + const val NOTIFICATION_CONNECTION_ALERT_ID = 2587 private const val CONNECTION_ALERT_SNOOZE_HOURS = 8 - private const val CONNECTION_ALERT_SNOOZE_DURATION_MILLIS = CONNECTION_ALERT_SNOOZE_HOURS * 60 * 60 * 1000L - private const val CONNECTION_ALERT_DISMISS_SNOOZE_MILLIS = 1 * 60 * 60 * 1000L /*1 hour*/ + private const val CONNECTION_ALERT_SNOOZE_MILLIS = CONNECTION_ALERT_SNOOZE_HOURS * ONE_HOUR_MILLIS + private const val CONNECTION_ALERT_DISMISS_MILLIS = ONE_HOUR_MILLIS private const val CONNECTION_ALERT_ACTION_DISMISS = "io.heckel.ntfy.CONNECTION_ALERT_DISMISS" private const val CONNECTION_ALERT_ACTION_SNOOZE = "io.heckel.ntfy.CONNECTION_ALERT_SNOOZE" private const val CONNECTION_ALERT_ACTION_NEVER = "io.heckel.ntfy.CONNECTION_ALERT_NEVER" diff --git a/app/src/main/java/io/heckel/ntfy/service/SubscriberServiceManager.kt b/app/src/main/java/io/heckel/ntfy/service/SubscriberServiceManager.kt index a8fe62bc..f577b10c 100644 --- a/app/src/main/java/io/heckel/ntfy/service/SubscriberServiceManager.kt +++ b/app/src/main/java/io/heckel/ntfy/service/SubscriberServiceManager.kt @@ -3,11 +3,11 @@ package io.heckel.ntfy.service import android.app.NotificationManager import android.content.Context import android.content.Intent -import io.heckel.ntfy.util.isNetworkAvailable import androidx.core.content.ContextCompat import androidx.work.* import io.heckel.ntfy.app.Application import io.heckel.ntfy.util.Log +import io.heckel.ntfy.util.isNetworkAvailable import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -65,7 +65,7 @@ class SubscriberServiceManager(private val context: Context) { if (!hasNetwork) { app.repository.clearConnectionDetails() val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - notificationManager.cancel(NOTIFICATION_CONNECTION_ALERT_ID) + notificationManager.cancel(SubscriberService.NOTIFICATION_CONNECTION_ALERT_ID) } Intent(context, SubscriberService::class.java).also { context.stopService(it) @@ -79,8 +79,6 @@ class SubscriberServiceManager(private val context: Context) { companion object { const val TAG = "NtfySubscriberMgr" const val WORK_NAME_ONCE = "ServiceStartWorkerOnce" - private const val NOTIFICATION_CONNECTION_ALERT_ID = 2587 // Same as SubscriberService - fun refresh(context: Context) { val manager = SubscriberServiceManager(context) diff --git a/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt index 6083ae70..4a1ceb87 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt @@ -11,7 +11,6 @@ import android.content.Intent import android.content.pm.PackageManager import android.net.ConnectivityManager import android.net.Network -import io.heckel.ntfy.util.isNetworkAvailable import android.os.Build import android.os.Bundle import android.provider.Settings @@ -67,6 +66,7 @@ import io.heckel.ntfy.util.displayName import io.heckel.ntfy.util.formatDateShort import io.heckel.ntfy.util.isDarkThemeOn import io.heckel.ntfy.util.isIgnoringBatteryOptimizations +import io.heckel.ntfy.util.isNetworkAvailable import io.heckel.ntfy.util.maybeSplitTopicUrl import io.heckel.ntfy.util.randomSubscriptionId import io.heckel.ntfy.util.shortUrl diff --git a/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt index 3ba01e0d..629aa922 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt @@ -10,9 +10,7 @@ import android.os.Build import android.os.Bundle import android.provider.Settings import android.provider.Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM -import android.text.TextUtils import android.view.View -import android.widget.Button import android.widget.Toast import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts @@ -42,7 +40,6 @@ import kotlinx.coroutines.launch import okhttp3.RequestBody.Companion.toRequestBody import java.text.SimpleDateFormat import java.util.* -import java.util.concurrent.TimeUnit /** * Main settings @@ -344,11 +341,11 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere when (pref.value.toLongOrNull() ?: repository.getConnectionAlertSeconds()) { Repository.CONNECTION_ALERT_NEVER -> getString(R.string.settings_advanced_connection_alert_summary_never) 30L -> "Alert after 30 seconds (testing)" - Repository.CONNECTION_ALERT_FIVE_MINUTES -> getString(R.string.settings_advanced_connection_alert_summary_five_minutes) - Repository.CONNECTION_ALERT_FIFTEEN_MINUTES -> getString(R.string.settings_advanced_connection_alert_summary_fifteen_minutes) - Repository.CONNECTION_ALERT_ONE_HOUR -> getString(R.string.settings_advanced_connection_alert_summary_one_hour) - Repository.CONNECTION_ALERT_THREE_HOURS -> getString(R.string.settings_advanced_connection_alert_summary_three_hours) - Repository.CONNECTION_ALERT_TWELVE_HOURS -> getString(R.string.settings_advanced_connection_alert_summary_twelve_hours) + Repository.CONNECTION_ALERT_FIVE_MINUTES_SECONDS -> getString(R.string.settings_advanced_connection_alert_summary_five_minutes) + Repository.CONNECTION_ALERT_FIFTEEN_MINUTES_SECONDS -> getString(R.string.settings_advanced_connection_alert_summary_fifteen_minutes) + Repository.CONNECTION_ALERT_ONE_HOUR_SECONDS -> getString(R.string.settings_advanced_connection_alert_summary_one_hour) + Repository.CONNECTION_ALERT_THREE_HOURS_SECONDS -> getString(R.string.settings_advanced_connection_alert_summary_three_hours) + Repository.CONNECTION_ALERT_TWELVE_HOURS_SECONDS -> getString(R.string.settings_advanced_connection_alert_summary_twelve_hours) else -> getString(R.string.settings_advanced_connection_alert_summary_never) // Must match default const } } diff --git a/app/src/main/java/io/heckel/ntfy/util/Util.kt b/app/src/main/java/io/heckel/ntfy/util/Util.kt index 7ef04d84..3c857c8e 100644 --- a/app/src/main/java/io/heckel/ntfy/util/Util.kt +++ b/app/src/main/java/io/heckel/ntfy/util/Util.kt @@ -54,13 +54,6 @@ import kotlin.math.abs import kotlin.math.absoluteValue import androidx.core.net.toUri -// We check for any active network, not specifically for internet connectivity, -// because the ntfy server may be on a LAN without internet access. -fun isNetworkAvailable(context: Context): Boolean { - val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager - return connectivityManager.activeNetwork != null -} - fun topicUrl(baseUrl: String, topic: String) = "${baseUrl}/${topic}" fun topicUrlUp(baseUrl: String, topic: String) = "${baseUrl}/${topic}?up=1" // UnifiedPush fun topicUrlJson(baseUrl: String, topic: String, since: String) = "${topicUrl(baseUrl, topic)}/json?since=$since" @@ -554,3 +547,10 @@ fun deriveNotificationId(baseUrl: String, topic: String, sequenceId: String): In val hash = composite.hashCode() return if (hash == 0 || hash == Int.MIN_VALUE) 1 else abs(hash) } + +// We check for any active network, not specifically for internet connectivity, +// because the ntfy server may be on a LAN without internet access. +fun isNetworkAvailable(context: Context): Boolean { + val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + return connectivityManager.activeNetwork != null +} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3e7fb3c2..c0782c20 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -372,19 +372,6 @@ After one week After one month After 3 months - Connection lost alert - Never alert when connection is lost - Alert after 5 minutes without connection - Alert after 15 minutes without connection - Alert after 1 hour without connection - Alert after 3 hours without connection - Alert after 12 hours without connection - Never - After 5 minutes - After 15 minutes - After 1 hour - After 3 hours - After 12 hours Keep alerting for highest priority Max priority notifications continuously alert until dismissed Max priority notifications only alert once @@ -430,6 +417,19 @@ Restore successful Restore failed: %1$s Advanced + Alert when disconnected + Never notify when the ntfy server cannot be reached + Notify if the ntfy server cannot be reached for more than 5 minutes + Notify if the ntfy server cannot be reached for more than 15 minutes + Notify if the ntfy server cannot be reached for more than 1 hour + Notify if the ntfy server cannot be reached for more than 3 hours + Notify if the ntfy server cannot be reached for more than 12 hours + Never + After 5 minutes + After 15 minutes + After 1 hour + After 3 hours + After 12 hours Broadcast messages Apps can receive incoming notifications as broadcasts Apps cannot receive notifications as broadcasts From 0f3f1bd3e1730fd7db6b0349643dd9b3c77beb46 Mon Sep 17 00:00:00 2001 From: Philipp Heckel Date: Fri, 3 Apr 2026 15:46:15 -0400 Subject: [PATCH 18/20] Do not auto cacnel --- app/src/main/AndroidManifest.xml | 2 +- .../java/io/heckel/ntfy/service/SubscriberService.kt | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 28c03a77..e585a747 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -132,7 +132,7 @@ android:enabled="true" android:exported="false"/> - + now) return @@ -361,15 +360,13 @@ class SubscriberService : Service() { val contentIntent = PendingIntent.getActivity(this, 0, Intent(this, MainActivity::class.java), PendingIntent.FLAG_IMMUTABLE) - val snoozeIntent = PendingIntent.getBroadcast(this, 0, Intent(this, ConnectionAlertBroadcastReceiver::class.java).apply { action = CONNECTION_ALERT_ACTION_SNOOZE }, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) - val disableIntent = PendingIntent.getBroadcast(this, 1, + val disableIntent = PendingIntent.getBroadcast(this, 0, Intent(this, ConnectionAlertBroadcastReceiver::class.java).apply { action = CONNECTION_ALERT_ACTION_NEVER }, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) - - val deleteIntent = PendingIntent.getBroadcast(this, 2, + val deleteIntent = PendingIntent.getBroadcast(this, 0, Intent(this, ConnectionAlertBroadcastReceiver::class.java).apply { action = CONNECTION_ALERT_ACTION_DISMISS }, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) @@ -380,7 +377,6 @@ class SubscriberService : Service() { .setContentText(text) .setStyle(NotificationCompat.BigTextStyle().bigText(text)) .setContentIntent(contentIntent) - .setAutoCancel(true) .setOnlyAlertOnce(true) .setDeleteIntent(deleteIntent) .addAction(NotificationCompat.Action.Builder(0, getString(R.string.connection_alert_action_snooze, CONNECTION_ALERT_SNOOZE_HOURS), snoozeIntent).build()) From 90112fbbd046212ade6a1490c7c9c7ae7709f304 Mon Sep 17 00:00:00 2001 From: Philipp Heckel Date: Fri, 3 Apr 2026 20:44:35 -0400 Subject: [PATCH 19/20] Add snooze short --- app/src/main/AndroidManifest.xml | 3 +- .../heckel/ntfy/service/SubscriberService.kt | 32 +++++++++++-------- .../io/heckel/ntfy/ui/SettingsActivity.kt | 1 - app/src/main/res/values/values.xml | 2 -- 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e585a747..4305b77a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -139,7 +139,8 @@ android:exported="false"> - + + diff --git a/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt b/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt index 86970d94..06b4a7c1 100644 --- a/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt +++ b/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt @@ -360,10 +360,13 @@ class SubscriberService : Service() { val contentIntent = PendingIntent.getActivity(this, 0, Intent(this, MainActivity::class.java), PendingIntent.FLAG_IMMUTABLE) - val snoozeIntent = PendingIntent.getBroadcast(this, 0, - Intent(this, ConnectionAlertBroadcastReceiver::class.java).apply { action = CONNECTION_ALERT_ACTION_SNOOZE }, + val snoozeShortIntent = PendingIntent.getBroadcast(this, 0, + Intent(this, ConnectionAlertBroadcastReceiver::class.java).apply { action = CONNECTION_ALERT_ACTION_SNOOZE_SHORT }, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) - val disableIntent = PendingIntent.getBroadcast(this, 0, + val snoozeIntent = PendingIntent.getBroadcast(this, 0, + Intent(this, ConnectionAlertBroadcastReceiver::class.java).apply { action = CONNECTION_ALERT_ACTION_SNOOZE_LONG }, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) + val neverAlertIntent = PendingIntent.getBroadcast(this, 0, Intent(this, ConnectionAlertBroadcastReceiver::class.java).apply { action = CONNECTION_ALERT_ACTION_NEVER }, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) val deleteIntent = PendingIntent.getBroadcast(this, 0, @@ -379,8 +382,9 @@ class SubscriberService : Service() { .setContentIntent(contentIntent) .setOnlyAlertOnce(true) .setDeleteIntent(deleteIntent) - .addAction(NotificationCompat.Action.Builder(0, getString(R.string.connection_alert_action_snooze, CONNECTION_ALERT_SNOOZE_HOURS), snoozeIntent).build()) - .addAction(NotificationCompat.Action.Builder(0, getString(R.string.connection_alert_action_never), disableIntent).build()) + .addAction(NotificationCompat.Action.Builder(0, getString(R.string.connection_alert_action_snooze, CONNECTION_ALERT_SNOOZE_SHORT_HOURS), snoozeShortIntent).build()) + .addAction(NotificationCompat.Action.Builder(0, getString(R.string.connection_alert_action_snooze, CONNECTION_ALERT_SNOOZE_LONG_HOURS), snoozeIntent).build()) + .addAction(NotificationCompat.Action.Builder(0, getString(R.string.connection_alert_action_never), neverAlertIntent).build()) .build() Log.d(TAG, "Showing connection alert notification") @@ -499,11 +503,11 @@ class SubscriberService : Service() { val repository = Repository.getInstance(context) val notificationManager = context.getSystemService(NOTIFICATION_SERVICE) as NotificationManager when (intent.action) { - CONNECTION_ALERT_ACTION_DISMISS -> { - repository.setConnectionAlertSnoozeUntilTime(System.currentTimeMillis() + CONNECTION_ALERT_DISMISS_MILLIS) + CONNECTION_ALERT_ACTION_DISMISS, CONNECTION_ALERT_ACTION_SNOOZE_SHORT -> { + repository.setConnectionAlertSnoozeUntilTime(System.currentTimeMillis() + CONNECTION_ALERT_SNOOZE_SHORT_MILLIS) } - CONNECTION_ALERT_ACTION_SNOOZE -> { - repository.setConnectionAlertSnoozeUntilTime(System.currentTimeMillis() + CONNECTION_ALERT_SNOOZE_MILLIS) + CONNECTION_ALERT_ACTION_SNOOZE_LONG -> { + repository.setConnectionAlertSnoozeUntilTime(System.currentTimeMillis() + CONNECTION_ALERT_SNOOZE_LONG_MILLIS) } CONNECTION_ALERT_ACTION_NEVER -> { repository.setConnectionAlertSeconds(Repository.CONNECTION_ALERT_NEVER) @@ -535,11 +539,13 @@ class SubscriberService : Service() { private const val NOTIFICATION_RECEIVED_WAKELOCK_TIMEOUT_MILLIS = 10 * 60 * 1000L /*10 minutes*/ const val NOTIFICATION_CONNECTION_ALERT_ID = 2587 - private const val CONNECTION_ALERT_SNOOZE_HOURS = 8 - private const val CONNECTION_ALERT_SNOOZE_MILLIS = CONNECTION_ALERT_SNOOZE_HOURS * ONE_HOUR_MILLIS - private const val CONNECTION_ALERT_DISMISS_MILLIS = ONE_HOUR_MILLIS + private const val CONNECTION_ALERT_SNOOZE_SHORT_HOURS = 1 + private const val CONNECTION_ALERT_SNOOZE_SHORT_MILLIS = CONNECTION_ALERT_SNOOZE_SHORT_HOURS * ONE_HOUR_MILLIS + private const val CONNECTION_ALERT_SNOOZE_LONG_HOURS = 8 + private const val CONNECTION_ALERT_SNOOZE_LONG_MILLIS = CONNECTION_ALERT_SNOOZE_LONG_HOURS * ONE_HOUR_MILLIS private const val CONNECTION_ALERT_ACTION_DISMISS = "io.heckel.ntfy.CONNECTION_ALERT_DISMISS" - private const val CONNECTION_ALERT_ACTION_SNOOZE = "io.heckel.ntfy.CONNECTION_ALERT_SNOOZE" + private const val CONNECTION_ALERT_ACTION_SNOOZE_SHORT = "io.heckel.ntfy.CONNECTION_ALERT_SNOOZE_SHORT" + private const val CONNECTION_ALERT_ACTION_SNOOZE_LONG = "io.heckel.ntfy.CONNECTION_ALERT_SNOOZE_LONG" private const val CONNECTION_ALERT_ACTION_NEVER = "io.heckel.ntfy.CONNECTION_ALERT_NEVER" } } diff --git a/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt index 629aa922..f7914af5 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt @@ -340,7 +340,6 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere connectionAlert?.summaryProvider = Preference.SummaryProvider { pref -> when (pref.value.toLongOrNull() ?: repository.getConnectionAlertSeconds()) { Repository.CONNECTION_ALERT_NEVER -> getString(R.string.settings_advanced_connection_alert_summary_never) - 30L -> "Alert after 30 seconds (testing)" Repository.CONNECTION_ALERT_FIVE_MINUTES_SECONDS -> getString(R.string.settings_advanced_connection_alert_summary_five_minutes) Repository.CONNECTION_ALERT_FIFTEEN_MINUTES_SECONDS -> getString(R.string.settings_advanced_connection_alert_summary_fifteen_minutes) Repository.CONNECTION_ALERT_ONE_HOUR_SECONDS -> getString(R.string.settings_advanced_connection_alert_summary_one_hour) diff --git a/app/src/main/res/values/values.xml b/app/src/main/res/values/values.xml index 19c732b1..0bb4bca9 100644 --- a/app/src/main/res/values/values.xml +++ b/app/src/main/res/values/values.xml @@ -170,7 +170,6 @@ @string/settings_advanced_connection_alert_never - After 30 seconds (testing) @string/settings_advanced_connection_alert_five_minutes @string/settings_advanced_connection_alert_fifteen_minutes @string/settings_advanced_connection_alert_one_hour @@ -179,7 +178,6 @@ 0 - 30 300 900 3600 From 0244c42ddeb6c3e5cd72107803e890bb565c295f Mon Sep 17 00:00:00 2001 From: Philipp Heckel Date: Fri, 3 Apr 2026 20:47:20 -0400 Subject: [PATCH 20/20] Rename foreground service channel name --- app/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c0782c20..5979fb69 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -28,7 +28,7 @@ Default - Subscription Service + Background service Connection alerts Listening for incoming notifications Subscribed to instant delivery topics