diff --git a/.gitignore b/.gitignore index 9f7351f..b6a1020 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,6 @@ build/* .vscode .vscode/* .clang-format +android/native/.gradle/ +android/native/build/ +android/native/local.properties diff --git a/android/NATIVE_ANDROID_APP_PLAN.md b/android/NATIVE_ANDROID_APP_PLAN.md index 83b1c3d..2153a49 100644 --- a/android/NATIVE_ANDROID_APP_PLAN.md +++ b/android/NATIVE_ANDROID_APP_PLAN.md @@ -183,24 +183,35 @@ Todo: ### 1. Native Projektbasis -- [ ] Neues natives Gradle-Projekt unter `/android/native` erstellen. -- [ ] Kotlin DSL fuer Gradle verwenden. -- [ ] App-ID `de.yourpart.app` oder separate Dev-ID `de.yourpart.native.dev` entscheiden. -- [ ] Produktflavors anlegen: `local`, `staging`, `production`. -- [ ] Build Types anlegen: `debug`, `release`. -- [ ] Versionierung definieren: `versionCode`, `versionName`, Git-Hash im Build. -- [ ] Compose aktivieren. -- [ ] Material 3 aktivieren. -- [ ] Hilt einrichten. -- [ ] Retrofit/OkHttp einrichten. -- [ ] JSON-Library festlegen und einrichten. -- [ ] Room einrichten. -- [ ] DataStore einrichten. -- [ ] Coil einrichten. -- [ ] WorkManager einrichten. -- [ ] FCM Dependency vorbereiten, aber noch nicht aktiv schalten. -- [ ] Lint, Detekt oder Ktlint einrichten. -- [ ] CI-Build-Script fuer native App definieren. +Stand 2026-07-09: + +- Native Projektbasis ist unter `/android/native` angelegt. +- Gradle Kotlin DSL ist aktiv. +- Separate Native-App-ID ist bewusst gewaehlt: `de.yourpart.nativeapp` mit Flavor-/Build-Type-Suffixen fuer parallele Installation. +- Flavors `local`, `staging`, `production` sind angelegt. +- Build Types `debug`, `release` sind angelegt. +- Versionierung ist initial definiert: `versionCode`, `versionName`, `BuildConfig.GIT_SHA`. +- Compose, Material 3, Hilt, Retrofit/OkHttp, Kotlinx Serialization JSON, Room, DataStore, Coil, WorkManager und FCM-Dependency sind im Projekt hinterlegt. +- Ein reproduzierbarer lokaler Build laeuft erfolgreich: `:app:assembleLocalDebug`. + +- [x] Neues natives Gradle-Projekt unter `/android/native` erstellen. +- [x] Kotlin DSL fuer Gradle verwenden. +- [x] App-ID `de.yourpart.app` oder separate Dev-ID `de.yourpart.native.dev` entscheiden. +- [x] Produktflavors anlegen: `local`, `staging`, `production`. +- [x] Build Types anlegen: `debug`, `release`. +- [x] Versionierung definieren: `versionCode`, `versionName`, Git-Hash im Build. +- [x] Compose aktivieren. +- [x] Material 3 aktivieren. +- [x] Hilt einrichten. +- [x] Retrofit/OkHttp einrichten. +- [x] JSON-Library festlegen und einrichten. +- [x] Room einrichten. +- [x] DataStore einrichten. +- [x] Coil einrichten. +- [x] WorkManager einrichten. +- [x] FCM Dependency vorbereiten, aber noch nicht aktiv schalten. +- [x] Lint, Detekt oder Ktlint einrichten. +- [x] CI-Build-Script fuer native App definieren. ### 2. Design System diff --git a/android/native/README.md b/android/native/README.md new file mode 100644 index 0000000..707b2d4 --- /dev/null +++ b/android/native/README.md @@ -0,0 +1,34 @@ +# YourPart Native Android + +Native Android-App fuer YourPart als paralleler Aufbau zur bestehenden Capacitor-App. + +## Entscheidungen + +- Pfad: `/android/native` +- Sprache: Kotlin +- UI: Jetpack Compose +- App-ID Debug/Local: `de.yourpart.nativeapp.local.debug` +- App-ID Production Release: `de.yourpart.nativeapp` +- minSdk: 26 +- compileSdk/targetSdk: 36 +- Flavors: `local`, `staging`, `production` +- Build Types: `debug`, `release` + +## Kommandos + +```bash +cd android/native +../android/gradlew -p . :app:assembleLocalDebug +../android/gradlew -p . :app:installLocalDebug +./scripts/assemble-local-debug.sh +``` + +## Emulator-Backend + +Der Android-Emulator erreicht den Host-Rechner ueber `10.0.2.2`. + +Der `local` Flavor nutzt: + +- API: `http://10.0.2.2:2020` +- Socket.IO: `http://10.0.2.2:2020` +- Daemon: `ws://10.0.2.2:2021` diff --git a/android/native/app/build.gradle.kts b/android/native/app/build.gradle.kts new file mode 100644 index 0000000..9c14e76 --- /dev/null +++ b/android/native/app/build.gradle.kts @@ -0,0 +1,122 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") + id("org.jetbrains.kotlin.plugin.compose") + id("com.google.dagger.hilt.android") + kotlin("kapt") +} + +val gitShaProvider = providers.exec { + workingDir = rootDir.resolve("../..") + commandLine("git", "rev-parse", "--short=12", "HEAD") +}.standardOutput.asText.map { it.trim().ifBlank { "dev" } } + +val gitSha: String = runCatching { gitShaProvider.get() }.getOrDefault("dev") + +android { + namespace = "de.yourpart.nativeapp" + compileSdk = 36 + + defaultConfig { + applicationId = "de.yourpart.nativeapp" + minSdk = 26 + targetSdk = 36 + versionCode = 1 + versionName = "0.1.0" + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + buildConfigField("String", "GIT_SHA", "\"$gitSha\"") + } + + flavorDimensions += "environment" + productFlavors { + create("local") { + dimension = "environment" + applicationIdSuffix = ".local" + versionNameSuffix = "-local" + buildConfigField("String", "API_BASE_URL", "\"http://10.0.2.2:2020\"") + buildConfigField("String", "SOCKET_IO_URL", "\"http://10.0.2.2:2020\"") + buildConfigField("String", "DAEMON_SOCKET_URL", "\"ws://10.0.2.2:2021\"") + } + create("staging") { + dimension = "environment" + applicationIdSuffix = ".staging" + versionNameSuffix = "-staging" + buildConfigField("String", "API_BASE_URL", "\"https://staging.your-part.de\"") + buildConfigField("String", "SOCKET_IO_URL", "\"https://staging.your-part.de\"") + buildConfigField("String", "DAEMON_SOCKET_URL", "\"wss://staging.your-part.de\"") + } + create("production") { + dimension = "environment" + buildConfigField("String", "API_BASE_URL", "\"https://www.your-part.de\"") + buildConfigField("String", "SOCKET_IO_URL", "\"https://www.your-part.de\"") + buildConfigField("String", "DAEMON_SOCKET_URL", "\"wss://www.your-part.de\"") + } + } + + buildTypes { + debug { + applicationIdSuffix = ".debug" + versionNameSuffix = "-debug" + } + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro", + ) + } + } + + buildFeatures { + buildConfig = true + compose = true + } + + lint { + abortOnError = true + checkReleaseBuilds = true + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlin { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_17) + } + } +} + +dependencies { + implementation("androidx.core:core-ktx:1.17.0") + implementation("androidx.activity:activity-compose:1.13.0") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.10.0") + implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.10.0") + implementation("androidx.navigation:navigation-compose:2.9.6") + implementation("androidx.compose.material3:material3:1.4.0") + implementation("androidx.compose.ui:ui") + implementation("androidx.compose.ui:ui-tooling-preview") + debugImplementation("androidx.compose.ui:ui-tooling") + + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0") + implementation("com.squareup.retrofit2:retrofit:3.0.0") + implementation("com.squareup.okhttp3:okhttp:5.3.2") + implementation("com.squareup.okhttp3:logging-interceptor:5.3.2") + implementation("com.google.dagger:hilt-android:2.57.2") + kapt("com.google.dagger:hilt-android-compiler:2.57.2") + implementation("androidx.room:room-runtime:2.6.1") + implementation("androidx.room:room-ktx:2.6.1") + kapt("androidx.room:room-compiler:2.6.1") + implementation("androidx.datastore:datastore-preferences:1.1.7") + implementation("io.coil-kt:coil-compose:2.6.0") + implementation("androidx.work:work-runtime-ktx:2.8.1") + implementation("com.google.firebase:firebase-messaging:24.1.2") + + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.3.0") + androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0") +} diff --git a/android/native/app/proguard-rules.pro b/android/native/app/proguard-rules.pro new file mode 100644 index 0000000..01ba94c --- /dev/null +++ b/android/native/app/proguard-rules.pro @@ -0,0 +1 @@ +# Keep release rules intentionally empty until native modules require them. diff --git a/android/native/app/src/main/AndroidManifest.xml b/android/native/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..fa9f522 --- /dev/null +++ b/android/native/app/src/main/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + diff --git a/android/native/app/src/main/java/de/yourpart/nativeapp/MainActivity.kt b/android/native/app/src/main/java/de/yourpart/nativeapp/MainActivity.kt new file mode 100644 index 0000000..27c76ac --- /dev/null +++ b/android/native/app/src/main/java/de/yourpart/nativeapp/MainActivity.kt @@ -0,0 +1,22 @@ +package de.yourpart.nativeapp + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import de.yourpart.nativeapp.ui.YourPartNativeApp +import de.yourpart.nativeapp.ui.theme.YourPartTheme +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + setContent { + YourPartTheme { + YourPartNativeApp() + } + } + } +} diff --git a/android/native/app/src/main/java/de/yourpart/nativeapp/YourPartNativeApplication.kt b/android/native/app/src/main/java/de/yourpart/nativeapp/YourPartNativeApplication.kt new file mode 100644 index 0000000..da67f8f --- /dev/null +++ b/android/native/app/src/main/java/de/yourpart/nativeapp/YourPartNativeApplication.kt @@ -0,0 +1,7 @@ +package de.yourpart.nativeapp + +import android.app.Application +import dagger.hilt.android.HiltAndroidApp + +@HiltAndroidApp +class YourPartNativeApplication : Application() diff --git a/android/native/app/src/main/java/de/yourpart/nativeapp/ui/YourPartNativeApp.kt b/android/native/app/src/main/java/de/yourpart/nativeapp/ui/YourPartNativeApp.kt new file mode 100644 index 0000000..aae0624 --- /dev/null +++ b/android/native/app/src/main/java/de/yourpart/nativeapp/ui/YourPartNativeApp.kt @@ -0,0 +1,68 @@ +package de.yourpart.nativeapp.ui + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import de.yourpart.nativeapp.BuildConfig + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun YourPartNativeApp() { + Scaffold( + topBar = { + TopAppBar( + title = { Text("YourPart") }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.surface, + titleContentColor = MaterialTheme.colorScheme.onSurface, + ), + ) + }, + ) { innerPadding -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(innerPadding) + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + Text( + text = "Native Android MVP", + style = MaterialTheme.typography.headlineSmall, + ) + Text( + text = "Projektbasis fuer Kotlin, Compose, Flavors, Hilt und Netzwerk ist angelegt.", + style = MaterialTheme.typography.bodyLarge, + ) + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.secondaryContainer, + ), + ) { + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + Text("Environment", style = MaterialTheme.typography.titleMedium) + Text("API: ${BuildConfig.API_BASE_URL}") + Text("Socket.IO: ${BuildConfig.SOCKET_IO_URL}") + Text("Daemon: ${BuildConfig.DAEMON_SOCKET_URL}") + } + } + } + } +} diff --git a/android/native/app/src/main/java/de/yourpart/nativeapp/ui/theme/Theme.kt b/android/native/app/src/main/java/de/yourpart/nativeapp/ui/theme/Theme.kt new file mode 100644 index 0000000..9317f12 --- /dev/null +++ b/android/native/app/src/main/java/de/yourpart/nativeapp/ui/theme/Theme.kt @@ -0,0 +1,27 @@ +package de.yourpart.nativeapp.ui.theme + +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color + +private val YourPartLightScheme = lightColorScheme( + primary = Color(0xFFF8A22B), + onPrimary = Color(0xFF2B1F14), + secondary = Color(0xFF78C38A), + onSecondary = Color(0xFF173822), + secondaryContainer = Color(0xFFE5F3E8), + onSecondaryContainer = Color(0xFF1F5131), + surface = Color(0xFFFFF8EE), + onSurface = Color(0xFF2B1F14), + background = Color(0xFFFFFCF7), + onBackground = Color(0xFF2B1F14), +) + +@Composable +fun YourPartTheme(content: @Composable () -> Unit) { + MaterialTheme( + colorScheme = YourPartLightScheme, + content = content, + ) +} diff --git a/android/native/app/src/main/res/drawable/ic_launcher_foreground.xml b/android/native/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..13a551d --- /dev/null +++ b/android/native/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,16 @@ + + + + + + diff --git a/android/native/app/src/main/res/values/colors.xml b/android/native/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..ab1465b --- /dev/null +++ b/android/native/app/src/main/res/values/colors.xml @@ -0,0 +1,4 @@ + + + #FFF8EE + diff --git a/android/native/app/src/main/res/values/strings.xml b/android/native/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..ae3e2a0 --- /dev/null +++ b/android/native/app/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + + YourPart Native + diff --git a/android/native/app/src/main/res/values/styles.xml b/android/native/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..f6fcc4f --- /dev/null +++ b/android/native/app/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + diff --git a/android/native/build.gradle.kts b/android/native/build.gradle.kts new file mode 100644 index 0000000..181707d --- /dev/null +++ b/android/native/build.gradle.kts @@ -0,0 +1,6 @@ +plugins { + id("com.android.application") version "8.13.0" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false + id("org.jetbrains.kotlin.plugin.compose") version "2.2.20" apply false + id("com.google.dagger.hilt.android") version "2.57.2" apply false +} diff --git a/android/native/gradle.properties b/android/native/gradle.properties new file mode 100644 index 0000000..d718106 --- /dev/null +++ b/android/native/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8 +android.useAndroidX=true +android.nonTransitiveRClass=true +kotlin.code.style=official diff --git a/android/native/scripts/assemble-local-debug.sh b/android/native/scripts/assemble-local-debug.sh new file mode 100755 index 0000000..6b17014 --- /dev/null +++ b/android/native/scripts/assemble-local-debug.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +"$PROJECT_DIR/../android/gradlew" -p "$PROJECT_DIR" :app:assembleLocalDebug diff --git a/android/native/settings.gradle.kts b/android/native/settings.gradle.kts new file mode 100644 index 0000000..97d91fa --- /dev/null +++ b/android/native/settings.gradle.kts @@ -0,0 +1,18 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} + +rootProject.name = "YourPartNative" +include(":app")