Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
[![Android 8+](https://img.shields.io/badge/Android-8.0%2B-3DDC84?logo=android&logoColor=white)](https://developer.android.com/about/versions/oreo)

<p align="center">
<img src="assets/branding/message487-icon.png" width="160" alt="Message487 app icon">
<img src="fastlane/metadata/android/en-US/images/icon.png" width="160" alt="Message487 app icon">
</p>

Message487 connects selected Android notifications and incoming SMS to your n8n workflows.
Expand Down
19 changes: 17 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ if (signingInputs.any { it != null } && signingInputs.any { it.isNullOrBlank() }
throw GradleException("Release signing environment is incomplete")
}

val gitCommitHash = providers.environmentVariable("GITHUB_SHA")
.orElse(providers.environmentVariable("MESSAGE487_GIT_COMMIT"))
.orElse(providers.exec {
workingDir(rootProject.projectDir)
commandLine("git", "rev-parse", "HEAD")
isIgnoreExitValue = true
}.standardOutput.asText)
.map { value -> value.trim().take(8).takeIf { it.matches(Regex("[0-9a-fA-F]{8}")) } ?: "unknown" }
.get()

android {
namespace = "life.andre.message487"
compileSdk = 36
Expand All @@ -21,8 +31,9 @@ android {
applicationId = "life.andre.message487"
minSdk = 26
targetSdk = 36
versionCode = 2
versionName = "0.0.2"
versionCode = 3
versionName = "0.0.3"
buildConfigField("String", "GIT_COMMIT_HASH", "\"$gitCommitHash\"")
}
signingConfigs {
if (signingInputs.all { !it.isNullOrBlank() }) {
Expand Down Expand Up @@ -50,6 +61,10 @@ android {
compose = true
buildConfig = true
}
dependenciesInfo {
includeInApk = false
includeInBundle = false
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
Expand Down
35 changes: 35 additions & 0 deletions app/src/main/java/life/andre/message487/AppInfo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package life.andre.message487

import android.content.ActivityNotFoundException
import android.content.Intent
import android.net.Uri
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource

@Composable
internal fun AppInfo() {
val context = LocalContext.current
var browserError by remember { mutableStateOf(false) }
Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
SupportingText(stringResource(R.string.app_version, BuildConfig.VERSION_NAME, BuildConfig.GIT_COMMIT_HASH))
TextButton(onClick = {
browserError = false
try {
context.startActivity(Intent(Intent.ACTION_VIEW,
Uri.parse("https://github.com/andre487/AndroidMessage487/blob/main/PRIVACY.md")))
} catch (_: ActivityNotFoundException) {
browserError = true
} catch (_: SecurityException) {
browserError = true
}
}, modifier = Modifier.fillMaxWidth()) {
Text(stringResource(R.string.privacy_policy))
}
if (browserError) Text(stringResource(R.string.no_browser), color = MaterialTheme.colorScheme.error)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,6 @@ internal fun ConnectionScreen(state: ConnectionState, model: ConnectionViewModel
}
}
item { SupportingText(stringResource(R.string.destination_note)) }
item { AppInfo() }
}
}
1 change: 1 addition & 0 deletions app/src/main/java/life/andre/message487/OverviewScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ internal fun OverviewScreen(settings: ForwardingSettings, permissions: Permissio
}
}
}
item { AppInfo() }
}
}

Expand Down
3 changes: 3 additions & 0 deletions app/src/main/res/values-ru/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,7 @@
<string name="auth_token">Токен веб-хука</string>
<string name="token_hint">Введите токен без префикса Bearer. Хранится на устройстве в зашифрованном виде.</string>
<string name="invalid_token">Введите токен без пробелов и префикса Bearer.</string>
<string name="app_version">Версия %1$s · %2$s</string>
<string name="privacy_policy">Политика конфиденциальности</string>
<string name="no_browser">Не удалось открыть политику конфиденциальности: браузер недоступен.</string>
</resources>
3 changes: 3 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,7 @@
<string name="auth_token">Webhook token</string>
<string name="token_hint">Enter the token without the Bearer prefix. Stored encrypted on this device.</string>
<string name="invalid_token">Enter a token without spaces or the Bearer prefix.</string>
<string name="app_version">Version %1$s · %2$s</string>
<string name="privacy_policy">Privacy policy</string>
<string name="no_browser">No browser is available to open the privacy policy.</string>
</resources>
15 changes: 15 additions & 0 deletions app/src/test/java/life/andre/message487/ScreenInteractionTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,21 @@ class ScreenInteractionTest {
node(R.string.connection_heading).assertIsDisplayed()
}

@Test fun `overview and connection show version and open the public privacy policy`() {
for (connection in listOf(false, true)) {
if (connection) compose.onNode(hasText(application.getString(R.string.connection_nav)) and hasClickAction()).performClick()
compose.onNode(hasScrollToIndexAction()).performScrollToNode(hasText(application.getString(R.string.privacy_policy)))
compose.onNodeWithText(application.getString(R.string.app_version, BuildConfig.VERSION_NAME, BuildConfig.GIT_COMMIT_HASH)).assertIsDisplayed()
node(R.string.privacy_policy).performClick()
compose.runOnIdle {
val intent = org.robolectric.Shadows.shadowOf(compose.activity).nextStartedActivity
assertNotNull(intent)
assertEquals(android.content.Intent.ACTION_VIEW, intent.action)
assertEquals("https://github.com/andre487/AndroidMessage487/blob/main/PRIVACY.md", intent.dataString)
}
}
}

@Test fun `invalid URL is rejected then valid connection persists across store recreation`() {
node(R.string.connection_nav).performClick()
node(R.string.webhook_url).performTextReplacement("not a URL")
Expand Down
11 changes: 7 additions & 4 deletions assets/branding/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

| Asset | Use |
| --- | --- |
| `message487-icon.png` | Google Play app icon, 512×512 RGBA PNG; also used in README |
| `message487-feature.png` | Google Play feature graphic, 1024×500 RGB PNG without alpha; shared by both locales |
| [`icon.png`](../../fastlane/metadata/android/en-US/images/icon.png) | Google Play app icon, 512×512 RGBA PNG; also used in README |
| [`featureGraphic.png`](../../fastlane/metadata/android/en-US/images/featureGraphic.png) | Google Play feature graphic, 1024×500 RGB PNG without alpha; shared by both locales |
| `message487-feature-master.png` | Original generated banner, retained for future exports |
| `screenshots/en/`, `screenshots/ru/` | Actual phone screenshots in English and Russian, 1080×1920 |
| `fastlane/metadata/android/{en-US,ru-RU}/images/phoneScreenshots/` | Actual phone screenshots in English and Russian, 1080×1920 |

Launcher PNGs and the adaptive foreground are copied from
[andre487/sms487](https://github.com/andre487/sms487/tree/d4aca0724c4d8c8cfcfe128c6df6cc93f64625f2/client/app/src/main).
Expand All @@ -20,7 +20,10 @@ The generation prompt is recorded in [feature-prompt.txt](feature-prompt.txt).
It extends the icon's device imagery with notification cards and workflow nodes and uses
no localized text or third-party service marks. It is promotional artwork, not an app screenshot.

For a Play listing, upload `message487-icon.png` as the app icon and `message487-feature.png`
Current store assets and localized descriptions have one canonical location under
[`fastlane/metadata/android`](../../fastlane/metadata/android), shared by F-Droid and Google Play.

For a Play listing, upload [`icon.png`](../../fastlane/metadata/android/en-US/images/icon.png) as the app icon and [`featureGraphic.png`](../../fastlane/metadata/android/en-US/images/featureGraphic.png)
as the feature graphic. The screenshots were captured from the debug app on the API 35 emulator with synthetic
local data, using Android app locales and a 1080×1920 display. Refresh them from the release
being submitted whenever its UI changes; do not use generated illustrations as screenshots. Follow the current
Expand Down
Binary file removed assets/branding/screenshots/en/overview.png
Binary file not shown.
Binary file removed assets/branding/screenshots/en/sources.png
Binary file not shown.
Binary file removed assets/branding/screenshots/ru/overview.png
Binary file not shown.
Binary file removed assets/branding/screenshots/ru/sources.png
Binary file not shown.
11 changes: 11 additions & 0 deletions docs/en/releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,14 @@ CI builds and verifies the artifacts; it does not upload to Google Play or requi
account. Native symbol packaging from MegaProxy is unnecessary here: this app has no native core.
The bundle signature verifier also rejects unsigned added entries, modified entries, missing
required bundle entries and unexpected certificates; its regression fixtures run in Android CI.

## F-Droid

Store descriptions, changelogs and artwork live in
[`fastlane/metadata/android`](../../fastlane/metadata/android). Update both locales
before tagging a release. The submission recipe is maintained in `fdroid/fdroiddata`,
not duplicated in this repository. Pin each build to the full release commit SHA,
use JDK 21, and compare against the versioned GitHub release APK with the expected
signing certificate. Keep dependency metadata disabled for both APKs and bundles.
A successful GitHub build alone does not establish reproducibility: the F-Droid
build and binary comparison must pass before marking that verification complete.
11 changes: 11 additions & 0 deletions docs/ru/releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,14 @@ Play не настроены. Архив нативных символов из
нативного ядра. Проверка подписи бандла отвергает добавленные неподписанные файлы,
изменённые файлы, отсутствие обязательных частей и чужой сертификат; регрессионные
тесты этой проверки входят в Android CI.

## F-Droid

Описания, списки изменений и графика магазина находятся в
[`fastlane/metadata/android`](../../fastlane/metadata/android). Обновляйте обе локали
перед созданием релизного тега. Рецепт заявки поддерживается в `fdroid/fdroiddata`,
без дублирования в этом репозитории. Для сборки указывайте полный SHA релизного
коммита и JDK 21; сравнивайте результат с APK конкретной версии из GitHub Releases
с проверкой ожидаемого сертификата подписи. Метаданные зависимостей должны оставаться
выключенными для APK и App Bundle. Успешной GitHub-сборки недостаточно для заявления
о воспроизводимости: должны пройти сборка F-Droid и сравнение бинарных файлов.
2 changes: 2 additions & 0 deletions fastlane/metadata/android/en-US/changelogs/3.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Prepare Message487 for F-Droid with localized store descriptions and artwork. Disable AGP dependency metadata in APKs and App Bundles. Update the n8n Telegram guide with a shared formatter, separate header lines and configurable event time formatting.
Show the app version with its commit hash and a privacy policy link on the overview and connection screens.
14 changes: 14 additions & 0 deletions fastlane/metadata/android/en-US/full_description.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Message487 forwards selected Android notifications and new incoming SMS to an HTTPS webhook you configure. Connect your phone to n8n workflows or another webhook service; no n8n account is required when using a different server.

• Choose which applications can forward notifications, or select all visible launcher apps.
• Enable SMS forwarding separately. Existing SMS history is not read.
• Authenticate requests with a Bearer token and optionally require an event acknowledgement.
• Keep events in an encrypted local outbox and retry temporary delivery failures.
• Pause forwarding, review delivery status, retry or delete queued events.
• Identify your phone using a custom device code.
• Use light or dark themes and English or Russian interfaces.
• View rotating local diagnostics and optionally share a report by email.

Notification and SMS capture are off by default. Android notification access and SMS permission are requested for the corresponding features. Android may hide sensitive notification content. The app lists visible launcher apps rather than requesting access to the entire installed-app inventory.

You need your own HTTPS endpoint and token. The app has no advertising, analytics or automatic crash uploads. Your endpoint and downstream services receive forwarded message contents and apply their own retention policies. Retried requests can produce duplicates; configure deduplication on your server if needed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions fastlane/metadata/android/en-US/short_description.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Forward notifications and SMS to n8n or your own webhook
1 change: 1 addition & 0 deletions fastlane/metadata/android/en-US/title.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Message487
2 changes: 2 additions & 0 deletions fastlane/metadata/android/ru-RU/changelogs/3.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Подготовка к F-Droid: добавлены описания и графика магазина на двух языках, отключены метаданные зависимостей AGP в APK и App Bundle. Инструкция n8n использует общий скрипт форматирования Telegram с отдельными строками заголовка и настройкой времени события.
На главной и в настройках подключения показаны версия приложения с хешем коммита и ссылка на политику конфиденциальности.
14 changes: 14 additions & 0 deletions fastlane/metadata/android/ru-RU/full_description.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Message487 пересылает выбранные уведомления Android и новые входящие SMS на указанный вами HTTPS-веб-хук. Подключите телефон к сценариям n8n или другому серверу; при использовании другого сервера аккаунт n8n не нужен.

• Выберите приложения для пересылки уведомлений или все видимые приложения с иконкой запуска.
• Отдельно включите пересылку SMS. История SMS не читается.
• Используйте Bearer-токен и при необходимости подтверждение приёма события.
• Храните события в зашифрованной локальной очереди с повторными попытками при временных сбоях.
• Приостанавливайте пересылку, проверяйте статусы, повторяйте или удаляйте события.
• Задайте собственное обозначение телефона.
• Выберите светлую или тёмную тему, русский или английский интерфейс.
• Просматривайте ротируемые локальные логи и по желанию отправляйте отчёт по почте.

Сбор уведомлений и SMS изначально выключен. Для соответствующих функций нужны доступ к уведомлениям и разрешение на получение SMS. Android может скрывать чувствительное содержимое уведомлений. Приложение показывает видимые приложения с иконкой запуска, не запрашивая доступ ко всему списку установленных пакетов.

Нужны ваш HTTPS-сервер и токен. В приложении нет рекламы, аналитики и автоматической отправки отчётов о сбоях. Настроенный сервер и последующие сервисы получают содержимое сообщений и хранят его по своим правилам. Повторные попытки могут создавать дубликаты; при необходимости настройте дедупликацию на сервере.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions fastlane/metadata/android/ru-RU/short_description.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Пересылка уведомлений и SMS в n8n или ваш веб-хук
1 change: 1 addition & 0 deletions fastlane/metadata/android/ru-RU/title.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Message487
Loading