diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..be3943c --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,14 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java +.cxx/ + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts new file mode 100644 index 0000000..b100a8d --- /dev/null +++ b/android/app/build.gradle.kts @@ -0,0 +1,46 @@ +plugins { + id("com.android.application") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.example.flutter_template" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.flutter_template" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + // minSdk = 23 required by flutter_secure_storage 9.x (see docs/setup_reference.md). + minSdk = 23 + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + } +} + +flutter { + source = "../.." +} diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..e048f86 --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java b/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java index d62ab0c..0092f92 100644 --- a/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java +++ b/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java @@ -25,6 +25,11 @@ public static void registerWith(@NonNull FlutterEngine flutterEngine) { } catch (Exception e) { Log.e(TAG, "Error registering plugin flutter_secure_storage, com.it_nomads.fluttersecurestorage.FlutterSecureStoragePlugin", e); } + try { + flutterEngine.getPlugins().add(new com.github.dart_lang.jni.JniPlugin()); + } catch (Exception e) { + Log.e(TAG, "Error registering plugin jni, com.github.dart_lang.jni.JniPlugin", e); + } try { flutterEngine.getPlugins().add(new dev.fluttercommunity.plus.packageinfo.PackageInfoPlugin()); } catch (Exception e) { diff --git a/android/app/src/main/kotlin/com/example/flutter_template/MainActivity.kt b/android/app/src/main/kotlin/com/example/flutter_template/MainActivity.kt new file mode 100644 index 0000000..660cdb0 --- /dev/null +++ b/android/app/src/main/kotlin/com/example/flutter_template/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.flutter_template + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/build.gradle.kts b/android/build.gradle.kts new file mode 100644 index 0000000..dbee657 --- /dev/null +++ b/android/build.gradle.kts @@ -0,0 +1,24 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = + rootProject.layout.buildDirectory + .dir("../../build") + .get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..e96108c --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,6 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +# This newDsl flag was added by the Flutter template +android.newDsl=false +# This builtInKotlin flag was added by the Flutter template +android.builtInKotlin=false diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..2d428bf --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-all.zip diff --git a/android/key.properties.example b/android/key.properties.example index fdc043f..bf58cba 100644 --- a/android/key.properties.example +++ b/android/key.properties.example @@ -2,6 +2,12 @@ # Copy this file to key.properties and fill in your values # NEVER commit key.properties to git! +# NOTE: The generated android/app/build.gradle.kts currently signs the release +# build with the debug keystore (Flutter's default). It does NOT yet read this +# file. Real release signing via key.properties is tracked in the follow-up +# issue #18 "Release signing: key.properties + keystore for make build-android". +# Keep this example as the reference format for when that is wired in. + storePassword=YOUR_KEYSTORE_PASSWORD keyPassword=YOUR_KEY_PASSWORD keyAlias=release diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts new file mode 100644 index 0000000..c21f0c5 --- /dev/null +++ b/android/settings.gradle.kts @@ -0,0 +1,26 @@ +pluginManagement { + val flutterSdkPath = + run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "9.0.1" apply false + id("org.jetbrains.kotlin.android") version "2.3.20" apply false +} + +include(":app") diff --git a/docs/plans/android_scaffolding.md b/docs/plans/android_scaffolding.md index 6603ae9..d5a41eb 100644 --- a/docs/plans/android_scaffolding.md +++ b/docs/plans/android_scaffolding.md @@ -154,7 +154,7 @@ No race conditions identified — all operations are synchronous and single-thre ## No-Gos (Out of Scope) -- [SEPARATE-SLUG] Release signing setup (creating a real `key.properties` + keystore and verifying `make build-android` / `flutter build apk --release`) — out of scope because it requires a real keystore and secrets that must not be committed. The acceptance criteria only require the debug build. **The release build (`make build-android`) is NOT verified by this fix**; it is deferred to a concrete follow-up issue, "Release signing: key.properties + keystore for make build-android" (owner: yudame), linked to #13 (resolves CONCERN 2). +- [SEPARATE-SLUG] Release signing setup (creating a real `key.properties` + keystore and verifying `make build-android` / `flutter build apk --release`) — out of scope because it requires a real keystore and secrets that must not be committed. The acceptance criteria only require the debug build. **The release build (`make build-android`) is NOT verified by this fix**; it is deferred to a concrete follow-up issue, "Release signing: key.properties + keystore for make build-android" (owner: yudame, #18), linked to #13 (resolves CONCERN 2). - [EXTERNAL] Installing/verifying the Android SDK, JDK, or licenses on a developer machine — a human/environment action, not a repo change. ## Update System @@ -184,7 +184,7 @@ No documentation site changes required. - [ ] `minSdk = 23` is set in `android/app/build.gradle.kts` (for `flutter_secure_storage`). - [ ] The existing 2 tracked files (`GeneratedPluginRegistrant.java`, `key.properties.example`) are preserved. - [ ] `git status` shows only new `android/` files added and `key.properties.example` unchanged. `GeneratedPluginRegistrant.java` may appear as `M` (content-identical or content-drifted regeneration is acceptable) — only a deletion of `key.properties.example` or an unexpected new top-level file fails the gate (resolves CONCERN 1). -- [ ] Release build (`make build-android` / `flutter build apk --release`) is NOT verified by this fix; it is explicitly deferred to the release-signing follow-up issue (resolves CONCERN 2). The debug build is the gate. +- [ ] Release build (`make build-android` / `flutter build apk --release`) is NOT verified by this fix; it is explicitly deferred to the release-signing follow-up issue #18 (resolves CONCERN 2). The debug build is the gate. ## Team Orchestration @@ -287,14 +287,14 @@ When this plan is executed, the lead agent orchestrates work using Task tools. T ## Open Questions 1. ~~**Toolchain pins source**~~ **RESOLVED (decision a)** — The issue's Solution Sketch cites pins (AGP 9.0.1, Gradle 9.1.0, JDK 17, Android SDK 36) that are not documented anywhere in the repo. Running `flutter create` and committing the generated Gradle files inherently hard-pins whatever the author's Flutter 3.44.8 emits, so the question was decided implicitly by Task 1. To make the committed toolchain explicit rather than emergent, Task 3 records the concrete generated AGP / Gradle wrapper / compileSdk / minSdk versions in the completion note (resolves CONCERN 3). No hard-pin override of the generated toolchain. -2. ~~**Release signing follow-up**~~ **RESOLVED (deferred)** — Deferred as a concrete follow-up issue, "Release signing: key.properties + keystore for make build-android" (owner: yudame), linked to #13. This fix verifies only the debug build; `make build-android` / `flutter build apk --release` is explicitly out of scope and NOT verified here (resolves CONCERN 2). +2. ~~**Release signing follow-up**~~ **RESOLVED (deferred)** — Deferred as a concrete follow-up issue, "Release signing: key.properties + keystore for make build-android" (owner: yudame, #18), linked to #13. This fix verifies only the debug build; `make build-android` / `flutter build apk --release` is explicitly out of scope and NOT verified here (resolves CONCERN 2). ## Implementation Notes (Revision Pass) Embedded resolutions for the do-plan-critique findings (commit 6e444db, verdict READY TO BUILD with concerns). Each concern's resolution is applied in the plan text and linked where relevant: - **CONCERN 1 (Risk & Robustness)**: Success criterion 6 and the Verification "Existing files preserved" row now explicitly permit `GeneratedPluginRegistrant.java` appearing as `M` (content-identical or content-drifted regeneration is acceptable), with only a `D ` deletion of `key.properties.example` or an unexpected new top-level file failing the gate. Task 1 carries the same note. The gate is no longer self-contradictory. -- **CONCERN 2 (Scope & Value)**: The plan is now explicit that `make build-android` / `flutter build apk --release` is NOT verified by this fix — the debug build is the gate. Deferred to a concrete follow-up issue, "Release signing: key.properties + keystore for make build-android" (owner: yudame, linked to #13), noted in No-Gos, Success Criteria, and resolved Open Question 2. +- **CONCERN 2 (Scope & Value)**: The plan is now explicit that `make build-android` / `flutter build apk --release` is NOT verified by this fix — the debug build is the gate. Deferred to a concrete follow-up issue, "Release signing: key.properties + keystore for make build-android" (owner: yudame, #18), noted in No-Gos, Success Criteria, and resolved Open Question 2. - **CONCERN 3 (Toolchain)**: Open Question 1 is resolved as decision (a) — accept the toolchain `flutter create` emits. Task 3 now captures and records the concrete generated AGP / Gradle wrapper / compileSdk / minSdk versions in the completion note, making the committed toolchain explicit rather than emergent. - **NIT (Scope)**: Verification and Success Criteria now state the applicationId is `com.example.flutter_template` — the "matches pubspec" criterion is a substring match (contains `flutter_template`), not literal equality, and the `com.example` prefix is accepted for a template. @@ -305,6 +305,6 @@ Embedded resolutions for the do-plan-critique findings (commit 6e444db, verdict Round 2 re-critique confirms the four round-1 resolutions are embedded in the plan text and close their concerns; no new findings. Verdict: **READY TO BUILD (no concerns)**. - **CONCERN 1 (GeneratedPluginRegistrant.java `M` tolerance)** — CLOSED. Success Criteria (line 186), Task 1 (line 229), and Verification (line 273) all explicitly permit `GeneratedPluginRegistrant.java` to appear as `M`; only a `D ` deletion of `key.properties.example` or an unexpected new top-level file fails the gate. The gate is no longer self-contradictory. -- **CONCERN 2 (release-build out-of-scope + follow-up)** — CLOSED. No-Gos (line 157), Success Criteria (line 187), and resolved Open Question 2 (line 290) all state the release build (`make build-android` / `flutter build apk --release`) is NOT verified by this fix and is deferred to the concrete follow-up issue "Release signing: key.properties + keystore for make build-android" (owner: yudame, linked to #13). +- **CONCERN 2 (release-build out-of-scope + follow-up)** — CLOSED. No-Gos (line 157), Success Criteria (line 187), and resolved Open Question 2 (line 290) all state the release build (`make build-android` / `flutter build apk --release`) is NOT verified by this fix and is deferred to the concrete follow-up issue "Release signing: key.properties + keystore for make build-android" (owner: yudame, #18). - **CONCERN 3 (toolchain decision (a))** — CLOSED. Open Question 1 (line 289) resolves to decision (a); Task 3 (line 251) captures the concrete generated AGP / Gradle wrapper / compileSdk / minSdk versions in the completion note, making the committed toolchain explicit rather than emergent. - **NIT (applicationId substring match)** — CLOSED. Success Criteria (line 183), Verification (line 272), and Task 3 (line 250) state the applicationId is `com.example.flutter_template`, accepted as a substring match (contains `flutter_template`), not literal equality. diff --git a/pubspec.lock b/pubspec.lock index eed9390..f6031ab 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -485,6 +485,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" + jni: + dependency: transitive + description: + name: jni + sha256: d2c361082d554d4593c3012e26f6b188f902acd291330f13d6427641a92b3da1 + url: "https://pub.dev" + source: hosted + version: "0.14.2" js: dependency: transitive description: @@ -761,18 +769,18 @@ packages: dependency: transitive description: name: sentry - sha256: "599701ca0693a74da361bc780b0752e1abc98226cf5095f6b069648116c896bb" + sha256: c2ecd8abe82e63cdcb6947f71320612ced56e10ba94db7529d85cc02be47cb3b url: "https://pub.dev" source: hosted - version: "8.14.2" + version: "9.27.0" sentry_flutter: dependency: "direct main" description: name: sentry_flutter - sha256: "5ba2cf40646a77d113b37a07bd69f61bb3ec8a73cbabe5537b05a7c89d2656f8" + sha256: ec89cc6ba939ca19155ea83900d9740a36544f50b3b6baf265518e3348fb0f50 url: "https://pub.dev" source: hosted - version: "8.14.2" + version: "9.27.0" shared_preferences: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index f685e41..a1983b1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -46,7 +46,7 @@ dependencies: json_annotation: ^4.9.0 # Monitoring (Production) - sentry_flutter: ^8.11.0 + sentry_flutter: ^9.27.0 dev_dependencies: flutter_test: