diff --git a/CLAUDE.md b/CLAUDE.md index 9b436bc..79f5603 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,11 +8,11 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co # First-time setup: generate the Gradle wrapper (requires Gradle installed) gradle wrapper --gradle-version 8.9 -# Build debug APK (inside nix dev shell) -nix develop --command gradle --no-daemon assembleDebug +# Build debug APK +./gradlew assembleDebug -# Build release APK — requires android-signing-env (sets signing vars + PUSH_GATEWAY_URL) -android-signing-env nix develop --command gradle --no-daemon assembleRelease +# Build release APK +./gradlew assembleRelease # Install on connected device/emulator ./gradlew installDebug @@ -24,20 +24,6 @@ android-signing-env nix develop --command gradle --no-daemon assembleRelease ./gradlew clean ``` -### Secrets setup (required before release build) - -```bash -# Creates symlinks for google-services.json, firebase-service-account.json, .mc/credentials.json -# Only needed once per boot (symlinks point into /run/agenix which is tmpfs) -matrix-android-secrets /home/ltadeu6/Projetos/Codigo/matrix-android -``` - -### Deploy Cloudflare Worker (push gateway) - -```bash -cloudflare-worker-env nix develop --command wrangler deploy --config gateway/wrangler.toml -``` - The project has no unit tests yet. Android Studio is the recommended way to open, sync, and run the project. ## Architecture diff --git a/README.md b/README.md index 38d60b7..3368c62 100644 --- a/README.md +++ b/README.md @@ -10,13 +10,6 @@ A minimal Android Matrix chat client with a terminal aesthetic — dark backgrou - Text-only chat (no images, no avatars) - Real-time sync via Matrix `/sync` long-polling - Persistent session (survives app restart) -- End-to-end encryption (E2EE) via libolm / Megolm -- SAS device verification -- Push notifications (foreground via sync loop; background via FCM + Cloudflare Worker) - -## Download - -Pre-built APKs are available on the [releases page](https://git.tadix.dev/ltadeu6/matrix-android/releases). ## Screenshots / UI description @@ -110,37 +103,27 @@ Enter your full Matrix username (`@user:homeserver.tld`) and password directly. ``` app/src/main/java/com/ltadeu6/matrix/ -├── MainActivity.kt — single Activity; handles SSO deep link +├── MainActivity.kt — single Activity; handles SSO deep link ├── navigation/ -│ └── AppNavigation.kt — Compose NavHost (Login → Rooms → Chat) +│ └── AppNavigation.kt — Compose NavHost (Login → Rooms → Chat) ├── auth/ -│ ├── LoginScreen.kt — login UI -│ └── LoginViewModel.kt — SSO URL builder, login calls +│ ├── LoginScreen.kt — login UI +│ └── LoginViewModel.kt — SSO URL builder, login calls ├── rooms/ -│ └── RoomListScreen.kt — joined rooms list +│ └── RoomListScreen.kt — joined rooms list ├── chat/ -│ ├── ChatScreen.kt — message list + input bar -│ └── ChatViewModel.kt — sends messages, maps sync state +│ ├── ChatScreen.kt — message list + input bar +│ └── ChatViewModel.kt — sends messages, maps sync state ├── matrix/ -│ ├── MatrixApi.kt — Retrofit interface (Matrix REST v3) -│ ├── MatrixModels.kt — serialization data classes + local models -│ ├── MatrixSession.kt — singleton: auth, sync loop, state flows -│ ├── CryptoService.kt — E2EE: Olm/Megolm, OTK management -│ ├── VerificationService.kt — SAS device verification -│ ├── NotificationHelper.kt — notification channel + posting -│ └── MatrixFirebaseMessagingService.kt — FCM push (app killed) -├── verification/ -│ └── VerificationScreen.kt — verification UI +│ ├── MatrixApi.kt — Retrofit interface (Matrix REST v3) +│ ├── MatrixModels.kt — serialization data classes + local models +│ └── MatrixSession.kt — singleton: auth, sync loop, state flows └── ui/theme/ - └── Theme.kt — terminal color scheme (green on black) + └── Theme.kt — terminal color scheme (green on black) ``` **State flow**: `MatrixSession` owns a `syncState: StateFlow` that all screens observe. The background sync loop continuously updates it via `/sync` long-polling. -## Push notifications - -When the app is in the foreground or background, notifications are fired directly from the sync loop. When the app is killed, push is delivered via FCM through a self-hosted Cloudflare Worker (`gateway/`) that acts as a Matrix push gateway. - ## Dependencies | Library | Purpose | @@ -152,10 +135,10 @@ When the app is in the foreground or background, notifications are fired directl | retrofit2-kotlinx-serialization-converter | Retrofit ↔ kotlinx bridge | | kotlinx.coroutines | Async / sync loop | | androidx.browser (Custom Tabs) | SSO browser login | -| libolm (native) | E2EE: Olm/Megolm encryption | -| Firebase Messaging | FCM push notifications | ## Known limitations +- No E2E encryption (plain-text transport only; suitable for unencrypted rooms) +- No push notifications - No media/file messages (text only) - Room list is not paginated (loads whatever `/sync` returns on first sync) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 494db06..bc605d0 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -14,19 +14,8 @@ android { applicationId = "com.ltadeu6.matrix" minSdk = 26 targetSdk = 35 - versionCode = 2 - versionName = "0.1.1" - buildConfigField("String", "PUSH_GATEWAY_URL", - "\"${System.getenv("PUSH_GATEWAY_URL") ?: ""}\"") - } - - signingConfigs { - create("release") { - storeFile = file(System.getenv("ANDROID_KEYSTORE_PATH") ?: "") - keyAlias = "release" - storePassword = System.getenv("ANDROID_KEYSTORE_PASSWORD") ?: "" - keyPassword = System.getenv("ANDROID_KEY_PASSWORD") ?: "" - } + versionCode = 1 + versionName = "0.1.0" } buildTypes { @@ -36,7 +25,6 @@ android { getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) - signingConfig = signingConfigs.getByName("release") } } @@ -51,7 +39,6 @@ android { buildFeatures { compose = true - buildConfig = true } packaging { @@ -81,5 +68,5 @@ dependencies { implementation(platform(libs.firebase.bom)) implementation(libs.firebase.messaging) debugImplementation(libs.androidx.ui.tooling) - implementation(libs.okhttp.logging) + debugImplementation(libs.okhttp.logging) } diff --git a/app/src/main/java/com/ltadeu6/matrix/matrix/MatrixSession.kt b/app/src/main/java/com/ltadeu6/matrix/matrix/MatrixSession.kt index 123b8f8..ca29ff2 100644 --- a/app/src/main/java/com/ltadeu6/matrix/matrix/MatrixSession.kt +++ b/app/src/main/java/com/ltadeu6/matrix/matrix/MatrixSession.kt @@ -25,7 +25,7 @@ import android.util.Log import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.Request -import com.ltadeu6.matrix.BuildConfig +import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicInteger @@ -1331,16 +1331,12 @@ class MatrixSession private constructor(private val appContext: Context) { } private fun buildApi(homeserver: String) { - val clientBuilder = OkHttpClient.Builder() + val logging = HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY } + val client = OkHttpClient.Builder() .connectTimeout(30, TimeUnit.SECONDS) .readTimeout(60, TimeUnit.SECONDS) - if (BuildConfig.DEBUG) { - val logging = okhttp3.logging.HttpLoggingInterceptor().apply { - level = okhttp3.logging.HttpLoggingInterceptor.Level.BODY - } - clientBuilder.addInterceptor(logging) - } - val client = clientBuilder.build() + .addInterceptor(logging) + .build() api = Retrofit.Builder() .baseUrl(homeserver) .client(client) @@ -1359,7 +1355,8 @@ class MatrixSession private constructor(private val appContext: Context) { companion object { private const val TAG = "MatrixCrypto" - val PUSH_GATEWAY_URL: String get() = BuildConfig.PUSH_GATEWAY_URL + /** Cloudflare Worker push gateway URL. */ + const val PUSH_GATEWAY_URL = "https://matrix-push-gateway.ltadeu6.workers.dev" @Volatile private var instance: MatrixSession? = null fun getInstance(context: Context): MatrixSession =