diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts new file mode 100644 index 00000000..c49cc980 --- /dev/null +++ b/android/app/build.gradle.kts @@ -0,0 +1,140 @@ +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.compose.compiler) + alias(libs.plugins.ksp) + alias(libs.plugins.hilt) + alias(libs.plugins.ktlint) + alias(libs.plugins.detekt) +} + +android { + namespace = "com.fabledsword.minstrel" + compileSdk = 35 + + defaultConfig { + applicationId = "com.fabledsword.minstrel" + minSdk = 26 + targetSdk = 35 + versionCode = 1 + versionName = "0.1.0-native" + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables { useSupportLibrary = true } + + ksp { + arg("room.schemaLocation", "$projectDir/schemas") + arg("room.incremental", "true") + } + } + + signingConfigs { + create("release") { + val keystorePath: String? = System.getenv("ANDROID_KEYSTORE_PATH") + if (!keystorePath.isNullOrEmpty()) { + storeFile = file(keystorePath) + storePassword = System.getenv("ANDROID_STORE_PASSWORD") + keyAlias = System.getenv("ANDROID_KEY_ALIAS") + keyPassword = System.getenv("ANDROID_KEY_PASSWORD") + } + } + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro", + ) + signingConfig = if (System.getenv("ANDROID_KEYSTORE_PATH").isNullOrEmpty()) { + signingConfigs.getByName("debug") + } else { + signingConfigs.getByName("release") + } + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + kotlinOptions { jvmTarget = "17" } + + buildFeatures { + compose = true + buildConfig = true + } + + packaging { + resources.excludes += setOf( + "/META-INF/{AL2.0,LGPL2.1}", + "META-INF/LICENSE.md", + "META-INF/LICENSE-notice.md", + ) + } +} + +detekt { + toolVersion = libs.versions.detekt.get() + config.setFrom(files("$rootDir/config/detekt.yml")) + buildUponDefaultConfig = true + parallel = true + autoCorrect = false +} + +dependencies { + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.lifecycle.runtime.compose) + implementation(libs.androidx.lifecycle.viewmodel.compose) + implementation(libs.androidx.activity.compose) + implementation(libs.androidx.nav.compose) + implementation(libs.androidx.hilt.nav.compose) + implementation(libs.androidx.hilt.work) + ksp(libs.androidx.hilt.compiler) + implementation(libs.androidx.work.runtime.ktx) + + implementation(platform(libs.compose.bom)) + implementation(libs.compose.ui) + implementation(libs.compose.ui.graphics) + implementation(libs.compose.material3) + debugImplementation(libs.compose.ui.tooling) + implementation(libs.compose.ui.tooling.preview) + + implementation(libs.hilt.android) + ksp(libs.hilt.compiler) + + implementation(libs.room.runtime) + implementation(libs.room.ktx) + ksp(libs.room.compiler) + + implementation(libs.retrofit) + implementation(libs.retrofit.kotlinx.serialization.converter) + implementation(libs.okhttp) + implementation(libs.okhttp.logging) + implementation(libs.okhttp.sse) + implementation(libs.kotlinx.serialization.json) + implementation(libs.kotlinx.coroutines.android) + implementation(libs.kotlinx.datetime) + + implementation(libs.media3.exoplayer) + implementation(libs.media3.session) + implementation(libs.media3.datasource.okhttp) + + implementation(libs.coil.compose) + implementation(libs.coil.network.okhttp) + + implementation(libs.timber) + + testImplementation(libs.junit.jupiter) + testImplementation(libs.turbine) + testImplementation(libs.mockk) + testImplementation(libs.kotlinx.coroutines.test) + testImplementation(libs.okhttp.mockwebserver) + + androidTestImplementation(platform(libs.compose.bom)) + androidTestImplementation(libs.compose.ui.test) + debugImplementation(libs.compose.ui.test.manifest) +} + +tasks.withType { useJUnitPlatform() } diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 00000000..837ed7eb --- /dev/null +++ b/android/app/proguard-rules.pro @@ -0,0 +1,19 @@ +# Keep the entry classes +-keep class com.fabledsword.minstrel.MainActivity { *; } +-keep class com.fabledsword.minstrel.MinstrelApplication { *; } + +# kotlinx.serialization (from the kotlinx.serialization README) +-keepattributes *Annotation*, InnerClasses +-dontnote kotlinx.serialization.AnnotationsKt +-keepclassmembers class kotlinx.serialization.json.** { + *** Companion; +} +-keepclasseswithmembers class kotlinx.serialization.json.** { + kotlinx.serialization.KSerializer serializer(...); +} + +# Hilt +-keep class * extends androidx.lifecycle.ViewModel { *; } + +# Media3 — keep player/exo classes +-keep class androidx.media3.** { *; } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..39c2080f --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/java/com/fabledsword/minstrel/MainActivity.kt b/android/app/src/main/java/com/fabledsword/minstrel/MainActivity.kt new file mode 100644 index 00000000..b413a0d6 --- /dev/null +++ b/android/app/src/main/java/com/fabledsword/minstrel/MainActivity.kt @@ -0,0 +1,29 @@ +package com.fabledsword.minstrel + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +// @AndroidEntryPoint added in Task 1.3 alongside @HiltAndroidApp on MinstrelApplication +// (Hilt KSP errors without an annotated Application class). +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + setContent { App() } + } +} + +@Composable +private fun App() { + Scaffold(modifier = Modifier.fillMaxSize()) { inner -> + Text(text = "Minstrel native — phase 1", modifier = Modifier.padding(inner)) + } +} 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 00000000..db77bb4b 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 00000000..17987b79 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 00000000..09d43914 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 00000000..d5f1c8d3 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 00000000..4d6372ee 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/strings.xml b/android/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..c924a36f --- /dev/null +++ b/android/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Minstrel + diff --git a/android/app/src/main/res/values/themes.xml b/android/app/src/main/res/values/themes.xml new file mode 100644 index 00000000..01500c8c --- /dev/null +++ b/android/app/src/main/res/values/themes.xml @@ -0,0 +1,8 @@ + + + + diff --git a/android/app/src/main/res/xml/backup_rules.xml b/android/app/src/main/res/xml/backup_rules.xml new file mode 100644 index 00000000..a1c2b2e8 --- /dev/null +++ b/android/app/src/main/res/xml/backup_rules.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/android/app/src/main/res/xml/data_extraction_rules.xml b/android/app/src/main/res/xml/data_extraction_rules.xml new file mode 100644 index 00000000..a0f223f7 --- /dev/null +++ b/android/app/src/main/res/xml/data_extraction_rules.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/android/config/detekt.yml b/android/config/detekt.yml new file mode 100644 index 00000000..fa88c3cf --- /dev/null +++ b/android/config/detekt.yml @@ -0,0 +1,3 @@ +# Minimal detekt config. Default rules apply via `buildUponDefaultConfig`. +build: + maxIssues: 0