diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts new file mode 100644 index 0000000..ff993ed --- /dev/null +++ b/androidApp/build.gradle.kts @@ -0,0 +1,125 @@ +import com.android.build.api.dsl.ApplicationExtension +import org.gradle.api.file.RegularFileProperty +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import java.util.Properties + +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.compose.multiplatform) + alias(libs.plugins.compose.compiler) + id("io.github.tarasovvp.kmp-secrets-plugin") version "1.3.0" +} + +val localProperties = + Properties().apply { + val file = rootProject.file("local.properties") + if (file.isFile) { + file.inputStream().use(::load) + } + } + +fun signingProperty(name: String): String? = + providers.gradleProperty(name).orNull + ?: providers.environmentVariable(name).orNull + ?: localProperties.getProperty(name) + +val derakkumaKeystoreFile = rootProject.file("derakkuma.keystore") +val derakkumaKeystorePassword = signingProperty("KEYSTORE_PASSWORD") +val derakkumaKeyAlias = signingProperty("KEY_ALIAS") ?: "derakkuma" +val derakkumaKeyPassword = signingProperty("KEY_PASSWORD") ?: derakkumaKeystorePassword +val requestedReleaseBuild = + gradle.startParameter.taskNames.any { taskName -> + taskName.contains("Release", ignoreCase = true) + } + +if (requestedReleaseBuild) { + require(derakkumaKeystoreFile.isFile) { + "Release builds must be signed with ${derakkumaKeystoreFile.path}" + } + require(!derakkumaKeystorePassword.isNullOrBlank()) { + "Set KEYSTORE_PASSWORD in local.properties, Gradle properties, or the environment" + } + require(!derakkumaKeyPassword.isNullOrBlank()) { + "Set KEY_PASSWORD in local.properties, Gradle properties, or the environment" + } +} + +kotlin { + target { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_17) + freeCompilerArgs.add("-Xexpect-actual-classes") + } + } +} + +dependencies { + implementation(project(":composeApp")) + implementation(libs.androidx.activity.compose) + implementation(libs.posthog.kmp) +} + +android { + namespace = "com.derakkuma" + compileSdk = 37 + + signingConfigs { + create("release") { + storeFile = derakkumaKeystoreFile + storePassword = derakkumaKeystorePassword.orEmpty() + keyAlias = derakkumaKeyAlias + keyPassword = derakkumaKeyPassword.orEmpty() + } + } + + defaultConfig { + applicationId = "com.derakkuma" + minSdk = 26 + targetSdk = 36 + versionCode = 2 + versionName = "0.1.1" + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + buildFeatures { + compose = true + } + + buildTypes { + debug { + applicationIdSuffix = ".debug" + isDebuggable = true + } + release { + signingConfig = signingConfigs.getByName("release") + isMinifyEnabled = true + isShrinkResources = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "../composeApp/proguard-rules.pro", + ) + } + } +} + +val emptyProguardMapFile = layout.buildDirectory.file("outputs/mapping/release/empty-proguard.map") +val writeEmptyProguardMap = + tasks.register("writeEmptyProguardMap") { + outputs.file(emptyProguardMapFile) + doLast { + emptyProguardMapFile.get().asFile.writeText("") + } + } + +tasks.matching { it.name == "packageReleaseBundle" }.configureEach { + dependsOn(writeEmptyProguardMap) + val mappingFileProperty = + javaClass.methods + .firstOrNull { it.name == "getObsfuscationMappingFile" } + ?.invoke(this) as? RegularFileProperty + mappingFileProperty?.set(emptyProguardMapFile) +} diff --git a/composeApp/src/androidMain/AndroidManifest.xml b/androidApp/src/main/AndroidManifest.xml similarity index 100% rename from composeApp/src/androidMain/AndroidManifest.xml rename to androidApp/src/main/AndroidManifest.xml diff --git a/composeApp/src/androidMain/assets/app-icon-assets/aoharukkuma_fg.png b/androidApp/src/main/assets/app-icon-assets/aoharukkuma_fg.png similarity index 100% rename from composeApp/src/androidMain/assets/app-icon-assets/aoharukkuma_fg.png rename to androidApp/src/main/assets/app-icon-assets/aoharukkuma_fg.png diff --git a/composeApp/src/androidMain/assets/app-icon-assets/aoharukkuma_mono.png b/androidApp/src/main/assets/app-icon-assets/aoharukkuma_mono.png similarity index 100% rename from composeApp/src/androidMain/assets/app-icon-assets/aoharukkuma_mono.png rename to androidApp/src/main/assets/app-icon-assets/aoharukkuma_mono.png diff --git a/composeApp/src/androidMain/assets/app-icon-assets/bg.png b/androidApp/src/main/assets/app-icon-assets/bg.png similarity index 100% rename from composeApp/src/androidMain/assets/app-icon-assets/bg.png rename to androidApp/src/main/assets/app-icon-assets/bg.png diff --git a/composeApp/src/androidMain/assets/app-icon-assets/default_fg.png b/androidApp/src/main/assets/app-icon-assets/default_fg.png similarity index 100% rename from composeApp/src/androidMain/assets/app-icon-assets/default_fg.png rename to androidApp/src/main/assets/app-icon-assets/default_fg.png diff --git a/composeApp/src/androidMain/assets/app-icon-assets/default_mono.png b/androidApp/src/main/assets/app-icon-assets/default_mono.png similarity index 100% rename from composeApp/src/androidMain/assets/app-icon-assets/default_mono.png rename to androidApp/src/main/assets/app-icon-assets/default_mono.png diff --git a/composeApp/src/androidMain/assets/app-icon-assets/doragokkuma_fg.png b/androidApp/src/main/assets/app-icon-assets/doragokkuma_fg.png similarity index 100% rename from composeApp/src/androidMain/assets/app-icon-assets/doragokkuma_fg.png rename to androidApp/src/main/assets/app-icon-assets/doragokkuma_fg.png diff --git a/composeApp/src/androidMain/assets/app-icon-assets/doragokkuma_mono.png b/androidApp/src/main/assets/app-icon-assets/doragokkuma_mono.png similarity index 100% rename from composeApp/src/androidMain/assets/app-icon-assets/doragokkuma_mono.png rename to androidApp/src/main/assets/app-icon-assets/doragokkuma_mono.png diff --git a/composeApp/src/androidMain/assets/app-icon-assets/enjekkuma_fg.png b/androidApp/src/main/assets/app-icon-assets/enjekkuma_fg.png similarity index 100% rename from composeApp/src/androidMain/assets/app-icon-assets/enjekkuma_fg.png rename to androidApp/src/main/assets/app-icon-assets/enjekkuma_fg.png diff --git a/composeApp/src/androidMain/assets/app-icon-assets/enjekkuma_mono.png b/androidApp/src/main/assets/app-icon-assets/enjekkuma_mono.png similarity index 100% rename from composeApp/src/androidMain/assets/app-icon-assets/enjekkuma_mono.png rename to androidApp/src/main/assets/app-icon-assets/enjekkuma_mono.png diff --git a/composeApp/src/androidMain/assets/app-icon-assets/higekkuma_fg.png b/androidApp/src/main/assets/app-icon-assets/higekkuma_fg.png similarity index 100% rename from composeApp/src/androidMain/assets/app-icon-assets/higekkuma_fg.png rename to androidApp/src/main/assets/app-icon-assets/higekkuma_fg.png diff --git a/composeApp/src/androidMain/assets/app-icon-assets/higekkuma_mono.png b/androidApp/src/main/assets/app-icon-assets/higekkuma_mono.png similarity index 100% rename from composeApp/src/androidMain/assets/app-icon-assets/higekkuma_mono.png rename to androidApp/src/main/assets/app-icon-assets/higekkuma_mono.png diff --git a/composeApp/src/androidMain/assets/app-icon-assets/kurobarakkuma_fg.png b/androidApp/src/main/assets/app-icon-assets/kurobarakkuma_fg.png similarity index 100% rename from composeApp/src/androidMain/assets/app-icon-assets/kurobarakkuma_fg.png rename to androidApp/src/main/assets/app-icon-assets/kurobarakkuma_fg.png diff --git a/composeApp/src/androidMain/assets/app-icon-assets/kurobarakkuma_mono.png b/androidApp/src/main/assets/app-icon-assets/kurobarakkuma_mono.png similarity index 100% rename from composeApp/src/androidMain/assets/app-icon-assets/kurobarakkuma_mono.png rename to androidApp/src/main/assets/app-icon-assets/kurobarakkuma_mono.png diff --git a/composeApp/src/androidMain/assets/app-icon-assets/lemonkkuma_fg.png b/androidApp/src/main/assets/app-icon-assets/lemonkkuma_fg.png similarity index 100% rename from composeApp/src/androidMain/assets/app-icon-assets/lemonkkuma_fg.png rename to androidApp/src/main/assets/app-icon-assets/lemonkkuma_fg.png diff --git a/composeApp/src/androidMain/assets/app-icon-assets/lemonkkuma_mono.png b/androidApp/src/main/assets/app-icon-assets/lemonkkuma_mono.png similarity index 100% rename from composeApp/src/androidMain/assets/app-icon-assets/lemonkkuma_mono.png rename to androidApp/src/main/assets/app-icon-assets/lemonkkuma_mono.png diff --git a/composeApp/src/androidMain/assets/app-icon-assets/limekkuma_fg.png b/androidApp/src/main/assets/app-icon-assets/limekkuma_fg.png similarity index 100% rename from composeApp/src/androidMain/assets/app-icon-assets/limekkuma_fg.png rename to androidApp/src/main/assets/app-icon-assets/limekkuma_fg.png diff --git a/composeApp/src/androidMain/assets/app-icon-assets/limekkuma_mono.png b/androidApp/src/main/assets/app-icon-assets/limekkuma_mono.png similarity index 100% rename from composeApp/src/androidMain/assets/app-icon-assets/limekkuma_mono.png rename to androidApp/src/main/assets/app-icon-assets/limekkuma_mono.png diff --git a/composeApp/src/androidMain/assets/app-icon-assets/meganekkuma_fg.png b/androidApp/src/main/assets/app-icon-assets/meganekkuma_fg.png similarity index 100% rename from composeApp/src/androidMain/assets/app-icon-assets/meganekkuma_fg.png rename to androidApp/src/main/assets/app-icon-assets/meganekkuma_fg.png diff --git a/composeApp/src/androidMain/assets/app-icon-assets/meganekkuma_mono.png b/androidApp/src/main/assets/app-icon-assets/meganekkuma_mono.png similarity index 100% rename from composeApp/src/androidMain/assets/app-icon-assets/meganekkuma_mono.png rename to androidApp/src/main/assets/app-icon-assets/meganekkuma_mono.png diff --git a/composeApp/src/androidMain/assets/app-icon-assets/mitsuyoshikkuma_fg.png b/androidApp/src/main/assets/app-icon-assets/mitsuyoshikkuma_fg.png similarity index 100% rename from composeApp/src/androidMain/assets/app-icon-assets/mitsuyoshikkuma_fg.png rename to androidApp/src/main/assets/app-icon-assets/mitsuyoshikkuma_fg.png diff --git a/composeApp/src/androidMain/assets/app-icon-assets/mitsuyoshikkuma_mono.png b/androidApp/src/main/assets/app-icon-assets/mitsuyoshikkuma_mono.png similarity index 100% rename from composeApp/src/androidMain/assets/app-icon-assets/mitsuyoshikkuma_mono.png rename to androidApp/src/main/assets/app-icon-assets/mitsuyoshikkuma_mono.png diff --git a/composeApp/src/androidMain/assets/app-icon-assets/ninjakkuma_fg.png b/androidApp/src/main/assets/app-icon-assets/ninjakkuma_fg.png similarity index 100% rename from composeApp/src/androidMain/assets/app-icon-assets/ninjakkuma_fg.png rename to androidApp/src/main/assets/app-icon-assets/ninjakkuma_fg.png diff --git a/composeApp/src/androidMain/assets/app-icon-assets/ninjakkuma_mono.png b/androidApp/src/main/assets/app-icon-assets/ninjakkuma_mono.png similarity index 100% rename from composeApp/src/androidMain/assets/app-icon-assets/ninjakkuma_mono.png rename to androidApp/src/main/assets/app-icon-assets/ninjakkuma_mono.png diff --git a/composeApp/src/androidMain/assets/app-icon-assets/onemukkuma_fg.png b/androidApp/src/main/assets/app-icon-assets/onemukkuma_fg.png similarity index 100% rename from composeApp/src/androidMain/assets/app-icon-assets/onemukkuma_fg.png rename to androidApp/src/main/assets/app-icon-assets/onemukkuma_fg.png diff --git a/composeApp/src/androidMain/assets/app-icon-assets/onemukkuma_mono.png b/androidApp/src/main/assets/app-icon-assets/onemukkuma_mono.png similarity index 100% rename from composeApp/src/androidMain/assets/app-icon-assets/onemukkuma_mono.png rename to androidApp/src/main/assets/app-icon-assets/onemukkuma_mono.png diff --git a/composeApp/src/androidMain/assets/app-icon-assets/pandakkuma_fg.png b/androidApp/src/main/assets/app-icon-assets/pandakkuma_fg.png similarity index 100% rename from composeApp/src/androidMain/assets/app-icon-assets/pandakkuma_fg.png rename to androidApp/src/main/assets/app-icon-assets/pandakkuma_fg.png diff --git a/composeApp/src/androidMain/assets/app-icon-assets/pandakkuma_mono.png b/androidApp/src/main/assets/app-icon-assets/pandakkuma_mono.png similarity index 100% rename from composeApp/src/androidMain/assets/app-icon-assets/pandakkuma_mono.png rename to androidApp/src/main/assets/app-icon-assets/pandakkuma_mono.png diff --git a/composeApp/src/androidMain/assets/app-icon-assets/risukkuma_fg.png b/androidApp/src/main/assets/app-icon-assets/risukkuma_fg.png similarity index 100% rename from composeApp/src/androidMain/assets/app-icon-assets/risukkuma_fg.png rename to androidApp/src/main/assets/app-icon-assets/risukkuma_fg.png diff --git a/composeApp/src/androidMain/assets/app-icon-assets/risukkuma_mono.png b/androidApp/src/main/assets/app-icon-assets/risukkuma_mono.png similarity index 100% rename from composeApp/src/androidMain/assets/app-icon-assets/risukkuma_mono.png rename to androidApp/src/main/assets/app-icon-assets/risukkuma_mono.png diff --git a/composeApp/src/androidMain/assets/app-icon-assets/tanukkuma_fg.png b/androidApp/src/main/assets/app-icon-assets/tanukkuma_fg.png similarity index 100% rename from composeApp/src/androidMain/assets/app-icon-assets/tanukkuma_fg.png rename to androidApp/src/main/assets/app-icon-assets/tanukkuma_fg.png diff --git a/composeApp/src/androidMain/assets/app-icon-assets/tanukkuma_mono.png b/androidApp/src/main/assets/app-icon-assets/tanukkuma_mono.png similarity index 100% rename from composeApp/src/androidMain/assets/app-icon-assets/tanukkuma_mono.png rename to androidApp/src/main/assets/app-icon-assets/tanukkuma_mono.png diff --git a/composeApp/src/androidMain/assets/app-icon-assets/yetikkuma_fg.png b/androidApp/src/main/assets/app-icon-assets/yetikkuma_fg.png similarity index 100% rename from composeApp/src/androidMain/assets/app-icon-assets/yetikkuma_fg.png rename to androidApp/src/main/assets/app-icon-assets/yetikkuma_fg.png diff --git a/composeApp/src/androidMain/assets/app-icon-assets/yetikkuma_mono.png b/androidApp/src/main/assets/app-icon-assets/yetikkuma_mono.png similarity index 100% rename from composeApp/src/androidMain/assets/app-icon-assets/yetikkuma_mono.png rename to androidApp/src/main/assets/app-icon-assets/yetikkuma_mono.png diff --git a/composeApp/src/androidMain/kotlin/com/derakkuma/MainActivity.kt b/androidApp/src/main/kotlin/com/derakkuma/MainActivity.kt similarity index 100% rename from composeApp/src/androidMain/kotlin/com/derakkuma/MainActivity.kt rename to androidApp/src/main/kotlin/com/derakkuma/MainActivity.kt diff --git a/composeApp/src/androidMain/res/bg.png b/androidApp/src/main/res/bg.png similarity index 100% rename from composeApp/src/androidMain/res/bg.png rename to androidApp/src/main/res/bg.png diff --git a/composeApp/src/androidMain/res/fg.png b/androidApp/src/main/res/fg.png similarity index 100% rename from composeApp/src/androidMain/res/fg.png rename to androidApp/src/main/res/fg.png diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml similarity index 100% rename from composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_aoharukkuma.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_aoharukkuma.xml similarity index 100% rename from composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_aoharukkuma.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_aoharukkuma.xml diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_aoharukkuma_round.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_aoharukkuma_round.xml similarity index 100% rename from composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_aoharukkuma_round.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_aoharukkuma_round.xml diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_doragokkuma.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_doragokkuma.xml similarity index 100% rename from composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_doragokkuma.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_doragokkuma.xml diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_doragokkuma_round.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_doragokkuma_round.xml similarity index 100% rename from composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_doragokkuma_round.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_doragokkuma_round.xml diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_enjekkuma.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_enjekkuma.xml similarity index 100% rename from composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_enjekkuma.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_enjekkuma.xml diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_enjekkuma_round.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_enjekkuma_round.xml similarity index 100% rename from composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_enjekkuma_round.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_enjekkuma_round.xml diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_higekkuma.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_higekkuma.xml similarity index 100% rename from composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_higekkuma.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_higekkuma.xml diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_higekkuma_round.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_higekkuma_round.xml similarity index 100% rename from composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_higekkuma_round.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_higekkuma_round.xml diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_kurobarakkuma.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_kurobarakkuma.xml similarity index 100% rename from composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_kurobarakkuma.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_kurobarakkuma.xml diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_kurobarakkuma_round.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_kurobarakkuma_round.xml similarity index 100% rename from composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_kurobarakkuma_round.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_kurobarakkuma_round.xml diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_lemonkkuma.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_lemonkkuma.xml similarity index 100% rename from composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_lemonkkuma.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_lemonkkuma.xml diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_lemonkkuma_round.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_lemonkkuma_round.xml similarity index 100% rename from composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_lemonkkuma_round.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_lemonkkuma_round.xml diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_limekkuma.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_limekkuma.xml similarity index 100% rename from composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_limekkuma.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_limekkuma.xml diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_limekkuma_round.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_limekkuma_round.xml similarity index 100% rename from composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_limekkuma_round.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_limekkuma_round.xml diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_meganekkuma.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_meganekkuma.xml similarity index 100% rename from composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_meganekkuma.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_meganekkuma.xml diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_meganekkuma_round.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_meganekkuma_round.xml similarity index 100% rename from composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_meganekkuma_round.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_meganekkuma_round.xml diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_mitsuyoshikkuma.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_mitsuyoshikkuma.xml similarity index 100% rename from composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_mitsuyoshikkuma.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_mitsuyoshikkuma.xml diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_mitsuyoshikkuma_round.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_mitsuyoshikkuma_round.xml similarity index 100% rename from composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_mitsuyoshikkuma_round.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_mitsuyoshikkuma_round.xml diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_ninjakkuma.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_ninjakkuma.xml similarity index 100% rename from composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_ninjakkuma.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_ninjakkuma.xml diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_ninjakkuma_round.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_ninjakkuma_round.xml similarity index 100% rename from composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_ninjakkuma_round.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_ninjakkuma_round.xml diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_onemukkuma.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_onemukkuma.xml similarity index 100% rename from composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_onemukkuma.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_onemukkuma.xml diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_onemukkuma_round.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_onemukkuma_round.xml similarity index 100% rename from composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_onemukkuma_round.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_onemukkuma_round.xml diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_pandakkuma.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_pandakkuma.xml similarity index 100% rename from composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_pandakkuma.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_pandakkuma.xml diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_pandakkuma_round.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_pandakkuma_round.xml similarity index 100% rename from composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_pandakkuma_round.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_pandakkuma_round.xml diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_risukkuma.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_risukkuma.xml similarity index 100% rename from composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_risukkuma.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_risukkuma.xml diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_risukkuma_round.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_risukkuma_round.xml similarity index 100% rename from composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_risukkuma_round.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_risukkuma_round.xml diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml similarity index 100% rename from composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_tanukkuma.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_tanukkuma.xml similarity index 100% rename from composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_tanukkuma.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_tanukkuma.xml diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_tanukkuma_round.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_tanukkuma_round.xml similarity index 100% rename from composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_tanukkuma_round.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_tanukkuma_round.xml diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_yetikkuma.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_yetikkuma.xml similarity index 100% rename from composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_yetikkuma.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_yetikkuma.xml diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_yetikkuma_round.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_yetikkuma_round.xml similarity index 100% rename from composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_yetikkuma_round.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_yetikkuma_round.xml diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_aoharukkuma.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_aoharukkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_aoharukkuma.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_aoharukkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_aoharukkuma_background.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_aoharukkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_aoharukkuma_background.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_aoharukkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_aoharukkuma_foreground.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_aoharukkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_aoharukkuma_foreground.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_aoharukkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_aoharukkuma_monochrome.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_aoharukkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_aoharukkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_aoharukkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_aoharukkuma_round.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_aoharukkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_aoharukkuma_round.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_aoharukkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_background.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_background.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_doragokkuma.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_doragokkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_doragokkuma.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_doragokkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_doragokkuma_background.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_doragokkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_doragokkuma_background.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_doragokkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_doragokkuma_foreground.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_doragokkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_doragokkuma_foreground.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_doragokkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_doragokkuma_monochrome.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_doragokkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_doragokkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_doragokkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_doragokkuma_round.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_doragokkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_doragokkuma_round.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_doragokkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_enjekkuma.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_enjekkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_enjekkuma.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_enjekkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_enjekkuma_background.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_enjekkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_enjekkuma_background.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_enjekkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_enjekkuma_foreground.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_enjekkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_enjekkuma_foreground.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_enjekkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_enjekkuma_monochrome.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_enjekkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_enjekkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_enjekkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_enjekkuma_round.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_enjekkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_enjekkuma_round.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_enjekkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_foreground.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_foreground.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_higekkuma.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_higekkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_higekkuma.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_higekkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_higekkuma_background.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_higekkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_higekkuma_background.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_higekkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_higekkuma_foreground.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_higekkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_higekkuma_foreground.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_higekkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_higekkuma_monochrome.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_higekkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_higekkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_higekkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_higekkuma_round.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_higekkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_higekkuma_round.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_higekkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_kurobarakkuma.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_kurobarakkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_kurobarakkuma.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_kurobarakkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_kurobarakkuma_background.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_kurobarakkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_kurobarakkuma_background.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_kurobarakkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_kurobarakkuma_foreground.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_kurobarakkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_kurobarakkuma_foreground.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_kurobarakkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_kurobarakkuma_monochrome.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_kurobarakkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_kurobarakkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_kurobarakkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_kurobarakkuma_round.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_kurobarakkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_kurobarakkuma_round.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_kurobarakkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_lemonkkuma.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_lemonkkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_lemonkkuma.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_lemonkkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_lemonkkuma_background.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_lemonkkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_lemonkkuma_background.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_lemonkkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_lemonkkuma_foreground.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_lemonkkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_lemonkkuma_foreground.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_lemonkkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_lemonkkuma_monochrome.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_lemonkkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_lemonkkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_lemonkkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_lemonkkuma_round.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_lemonkkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_lemonkkuma_round.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_lemonkkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_limekkuma.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_limekkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_limekkuma.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_limekkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_limekkuma_background.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_limekkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_limekkuma_background.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_limekkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_limekkuma_foreground.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_limekkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_limekkuma_foreground.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_limekkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_limekkuma_monochrome.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_limekkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_limekkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_limekkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_limekkuma_round.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_limekkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_limekkuma_round.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_limekkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_meganekkuma.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_meganekkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_meganekkuma.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_meganekkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_meganekkuma_background.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_meganekkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_meganekkuma_background.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_meganekkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_meganekkuma_foreground.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_meganekkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_meganekkuma_foreground.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_meganekkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_meganekkuma_monochrome.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_meganekkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_meganekkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_meganekkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_meganekkuma_round.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_meganekkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_meganekkuma_round.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_meganekkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_mitsuyoshikkuma.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_mitsuyoshikkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_mitsuyoshikkuma.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_mitsuyoshikkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_mitsuyoshikkuma_background.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_mitsuyoshikkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_mitsuyoshikkuma_background.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_mitsuyoshikkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_mitsuyoshikkuma_foreground.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_mitsuyoshikkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_mitsuyoshikkuma_foreground.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_mitsuyoshikkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_mitsuyoshikkuma_monochrome.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_mitsuyoshikkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_mitsuyoshikkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_mitsuyoshikkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_mitsuyoshikkuma_round.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_mitsuyoshikkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_mitsuyoshikkuma_round.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_mitsuyoshikkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_monochrome.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_monochrome.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_ninjakkuma.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_ninjakkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_ninjakkuma.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_ninjakkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_ninjakkuma_background.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_ninjakkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_ninjakkuma_background.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_ninjakkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_ninjakkuma_foreground.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_ninjakkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_ninjakkuma_foreground.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_ninjakkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_ninjakkuma_monochrome.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_ninjakkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_ninjakkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_ninjakkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_ninjakkuma_round.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_ninjakkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_ninjakkuma_round.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_ninjakkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_onemukkuma.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_onemukkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_onemukkuma.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_onemukkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_onemukkuma_background.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_onemukkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_onemukkuma_background.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_onemukkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_onemukkuma_foreground.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_onemukkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_onemukkuma_foreground.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_onemukkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_onemukkuma_monochrome.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_onemukkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_onemukkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_onemukkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_onemukkuma_round.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_onemukkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_onemukkuma_round.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_onemukkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_pandakkuma.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_pandakkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_pandakkuma.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_pandakkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_pandakkuma_background.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_pandakkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_pandakkuma_background.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_pandakkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_pandakkuma_foreground.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_pandakkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_pandakkuma_foreground.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_pandakkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_pandakkuma_monochrome.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_pandakkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_pandakkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_pandakkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_pandakkuma_round.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_pandakkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_pandakkuma_round.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_pandakkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_risukkuma.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_risukkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_risukkuma.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_risukkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_risukkuma_background.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_risukkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_risukkuma_background.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_risukkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_risukkuma_foreground.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_risukkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_risukkuma_foreground.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_risukkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_risukkuma_monochrome.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_risukkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_risukkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_risukkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_risukkuma_round.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_risukkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_risukkuma_round.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_risukkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_tanukkuma.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_tanukkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_tanukkuma.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_tanukkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_tanukkuma_background.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_tanukkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_tanukkuma_background.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_tanukkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_tanukkuma_foreground.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_tanukkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_tanukkuma_foreground.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_tanukkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_tanukkuma_monochrome.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_tanukkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_tanukkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_tanukkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_tanukkuma_round.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_tanukkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_tanukkuma_round.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_tanukkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_yetikkuma.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_yetikkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_yetikkuma.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_yetikkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_yetikkuma_background.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_yetikkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_yetikkuma_background.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_yetikkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_yetikkuma_foreground.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_yetikkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_yetikkuma_foreground.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_yetikkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_yetikkuma_monochrome.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_yetikkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_yetikkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_yetikkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_yetikkuma_round.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_yetikkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_yetikkuma_round.webp rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_yetikkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_aoharukkuma.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_aoharukkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_aoharukkuma.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_aoharukkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_aoharukkuma_background.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_aoharukkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_aoharukkuma_background.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_aoharukkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_aoharukkuma_foreground.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_aoharukkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_aoharukkuma_foreground.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_aoharukkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_aoharukkuma_monochrome.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_aoharukkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_aoharukkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_aoharukkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_aoharukkuma_round.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_aoharukkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_aoharukkuma_round.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_aoharukkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_background.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_background.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_doragokkuma.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_doragokkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_doragokkuma.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_doragokkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_doragokkuma_background.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_doragokkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_doragokkuma_background.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_doragokkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_doragokkuma_foreground.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_doragokkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_doragokkuma_foreground.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_doragokkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_doragokkuma_monochrome.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_doragokkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_doragokkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_doragokkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_doragokkuma_round.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_doragokkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_doragokkuma_round.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_doragokkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_enjekkuma.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_enjekkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_enjekkuma.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_enjekkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_enjekkuma_background.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_enjekkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_enjekkuma_background.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_enjekkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_enjekkuma_foreground.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_enjekkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_enjekkuma_foreground.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_enjekkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_enjekkuma_monochrome.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_enjekkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_enjekkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_enjekkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_enjekkuma_round.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_enjekkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_enjekkuma_round.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_enjekkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_foreground.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_foreground.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_higekkuma.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_higekkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_higekkuma.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_higekkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_higekkuma_background.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_higekkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_higekkuma_background.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_higekkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_higekkuma_foreground.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_higekkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_higekkuma_foreground.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_higekkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_higekkuma_monochrome.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_higekkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_higekkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_higekkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_higekkuma_round.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_higekkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_higekkuma_round.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_higekkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_kurobarakkuma.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_kurobarakkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_kurobarakkuma.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_kurobarakkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_kurobarakkuma_background.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_kurobarakkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_kurobarakkuma_background.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_kurobarakkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_kurobarakkuma_foreground.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_kurobarakkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_kurobarakkuma_foreground.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_kurobarakkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_kurobarakkuma_monochrome.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_kurobarakkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_kurobarakkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_kurobarakkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_kurobarakkuma_round.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_kurobarakkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_kurobarakkuma_round.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_kurobarakkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_lemonkkuma.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_lemonkkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_lemonkkuma.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_lemonkkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_lemonkkuma_background.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_lemonkkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_lemonkkuma_background.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_lemonkkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_lemonkkuma_foreground.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_lemonkkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_lemonkkuma_foreground.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_lemonkkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_lemonkkuma_monochrome.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_lemonkkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_lemonkkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_lemonkkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_lemonkkuma_round.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_lemonkkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_lemonkkuma_round.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_lemonkkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_limekkuma.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_limekkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_limekkuma.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_limekkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_limekkuma_background.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_limekkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_limekkuma_background.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_limekkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_limekkuma_foreground.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_limekkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_limekkuma_foreground.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_limekkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_limekkuma_monochrome.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_limekkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_limekkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_limekkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_limekkuma_round.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_limekkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_limekkuma_round.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_limekkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_meganekkuma.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_meganekkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_meganekkuma.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_meganekkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_meganekkuma_background.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_meganekkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_meganekkuma_background.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_meganekkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_meganekkuma_foreground.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_meganekkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_meganekkuma_foreground.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_meganekkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_meganekkuma_monochrome.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_meganekkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_meganekkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_meganekkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_meganekkuma_round.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_meganekkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_meganekkuma_round.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_meganekkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_mitsuyoshikkuma.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_mitsuyoshikkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_mitsuyoshikkuma.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_mitsuyoshikkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_mitsuyoshikkuma_background.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_mitsuyoshikkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_mitsuyoshikkuma_background.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_mitsuyoshikkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_mitsuyoshikkuma_foreground.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_mitsuyoshikkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_mitsuyoshikkuma_foreground.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_mitsuyoshikkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_mitsuyoshikkuma_monochrome.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_mitsuyoshikkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_mitsuyoshikkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_mitsuyoshikkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_mitsuyoshikkuma_round.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_mitsuyoshikkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_mitsuyoshikkuma_round.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_mitsuyoshikkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_monochrome.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_monochrome.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_ninjakkuma.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_ninjakkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_ninjakkuma.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_ninjakkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_ninjakkuma_background.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_ninjakkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_ninjakkuma_background.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_ninjakkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_ninjakkuma_foreground.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_ninjakkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_ninjakkuma_foreground.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_ninjakkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_ninjakkuma_monochrome.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_ninjakkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_ninjakkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_ninjakkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_ninjakkuma_round.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_ninjakkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_ninjakkuma_round.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_ninjakkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_onemukkuma.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_onemukkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_onemukkuma.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_onemukkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_onemukkuma_background.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_onemukkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_onemukkuma_background.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_onemukkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_onemukkuma_foreground.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_onemukkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_onemukkuma_foreground.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_onemukkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_onemukkuma_monochrome.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_onemukkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_onemukkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_onemukkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_onemukkuma_round.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_onemukkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_onemukkuma_round.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_onemukkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_pandakkuma.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_pandakkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_pandakkuma.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_pandakkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_pandakkuma_background.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_pandakkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_pandakkuma_background.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_pandakkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_pandakkuma_foreground.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_pandakkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_pandakkuma_foreground.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_pandakkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_pandakkuma_monochrome.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_pandakkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_pandakkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_pandakkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_pandakkuma_round.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_pandakkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_pandakkuma_round.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_pandakkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_risukkuma.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_risukkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_risukkuma.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_risukkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_risukkuma_background.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_risukkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_risukkuma_background.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_risukkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_risukkuma_foreground.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_risukkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_risukkuma_foreground.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_risukkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_risukkuma_monochrome.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_risukkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_risukkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_risukkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_risukkuma_round.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_risukkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_risukkuma_round.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_risukkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_tanukkuma.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_tanukkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_tanukkuma.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_tanukkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_tanukkuma_background.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_tanukkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_tanukkuma_background.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_tanukkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_tanukkuma_foreground.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_tanukkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_tanukkuma_foreground.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_tanukkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_tanukkuma_monochrome.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_tanukkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_tanukkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_tanukkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_tanukkuma_round.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_tanukkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_tanukkuma_round.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_tanukkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_yetikkuma.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_yetikkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_yetikkuma.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_yetikkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_yetikkuma_background.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_yetikkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_yetikkuma_background.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_yetikkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_yetikkuma_foreground.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_yetikkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_yetikkuma_foreground.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_yetikkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_yetikkuma_monochrome.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_yetikkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_yetikkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_yetikkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_yetikkuma_round.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_yetikkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_yetikkuma_round.webp rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_yetikkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_aoharukkuma.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_aoharukkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_aoharukkuma.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_aoharukkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_aoharukkuma_background.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_aoharukkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_aoharukkuma_background.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_aoharukkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_aoharukkuma_foreground.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_aoharukkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_aoharukkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_aoharukkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_aoharukkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_aoharukkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_aoharukkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_aoharukkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_aoharukkuma_round.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_aoharukkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_aoharukkuma_round.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_aoharukkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_background.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_background.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_doragokkuma.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_doragokkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_doragokkuma.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_doragokkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_doragokkuma_background.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_doragokkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_doragokkuma_background.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_doragokkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_doragokkuma_foreground.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_doragokkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_doragokkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_doragokkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_doragokkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_doragokkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_doragokkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_doragokkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_doragokkuma_round.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_doragokkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_doragokkuma_round.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_doragokkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_enjekkuma.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_enjekkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_enjekkuma.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_enjekkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_enjekkuma_background.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_enjekkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_enjekkuma_background.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_enjekkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_enjekkuma_foreground.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_enjekkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_enjekkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_enjekkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_enjekkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_enjekkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_enjekkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_enjekkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_enjekkuma_round.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_enjekkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_enjekkuma_round.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_enjekkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_foreground.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_foreground.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_higekkuma.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_higekkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_higekkuma.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_higekkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_higekkuma_background.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_higekkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_higekkuma_background.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_higekkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_higekkuma_foreground.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_higekkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_higekkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_higekkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_higekkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_higekkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_higekkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_higekkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_higekkuma_round.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_higekkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_higekkuma_round.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_higekkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_kurobarakkuma.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_kurobarakkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_kurobarakkuma.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_kurobarakkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_kurobarakkuma_background.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_kurobarakkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_kurobarakkuma_background.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_kurobarakkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_kurobarakkuma_foreground.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_kurobarakkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_kurobarakkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_kurobarakkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_kurobarakkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_kurobarakkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_kurobarakkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_kurobarakkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_kurobarakkuma_round.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_kurobarakkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_kurobarakkuma_round.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_kurobarakkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_lemonkkuma.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_lemonkkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_lemonkkuma.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_lemonkkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_lemonkkuma_background.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_lemonkkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_lemonkkuma_background.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_lemonkkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_lemonkkuma_foreground.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_lemonkkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_lemonkkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_lemonkkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_lemonkkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_lemonkkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_lemonkkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_lemonkkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_lemonkkuma_round.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_lemonkkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_lemonkkuma_round.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_lemonkkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_limekkuma.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_limekkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_limekkuma.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_limekkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_limekkuma_background.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_limekkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_limekkuma_background.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_limekkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_limekkuma_foreground.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_limekkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_limekkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_limekkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_limekkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_limekkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_limekkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_limekkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_limekkuma_round.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_limekkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_limekkuma_round.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_limekkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_meganekkuma.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_meganekkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_meganekkuma.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_meganekkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_meganekkuma_background.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_meganekkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_meganekkuma_background.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_meganekkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_meganekkuma_foreground.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_meganekkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_meganekkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_meganekkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_meganekkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_meganekkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_meganekkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_meganekkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_meganekkuma_round.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_meganekkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_meganekkuma_round.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_meganekkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_mitsuyoshikkuma.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_mitsuyoshikkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_mitsuyoshikkuma.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_mitsuyoshikkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_mitsuyoshikkuma_background.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_mitsuyoshikkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_mitsuyoshikkuma_background.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_mitsuyoshikkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_mitsuyoshikkuma_foreground.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_mitsuyoshikkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_mitsuyoshikkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_mitsuyoshikkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_mitsuyoshikkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_mitsuyoshikkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_mitsuyoshikkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_mitsuyoshikkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_mitsuyoshikkuma_round.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_mitsuyoshikkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_mitsuyoshikkuma_round.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_mitsuyoshikkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_monochrome.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_monochrome.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_ninjakkuma.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_ninjakkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_ninjakkuma.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_ninjakkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_ninjakkuma_background.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_ninjakkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_ninjakkuma_background.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_ninjakkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_ninjakkuma_foreground.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_ninjakkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_ninjakkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_ninjakkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_ninjakkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_ninjakkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_ninjakkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_ninjakkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_ninjakkuma_round.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_ninjakkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_ninjakkuma_round.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_ninjakkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_onemukkuma.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_onemukkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_onemukkuma.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_onemukkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_onemukkuma_background.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_onemukkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_onemukkuma_background.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_onemukkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_onemukkuma_foreground.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_onemukkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_onemukkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_onemukkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_onemukkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_onemukkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_onemukkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_onemukkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_onemukkuma_round.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_onemukkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_onemukkuma_round.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_onemukkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_pandakkuma.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_pandakkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_pandakkuma.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_pandakkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_pandakkuma_background.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_pandakkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_pandakkuma_background.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_pandakkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_pandakkuma_foreground.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_pandakkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_pandakkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_pandakkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_pandakkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_pandakkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_pandakkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_pandakkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_pandakkuma_round.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_pandakkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_pandakkuma_round.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_pandakkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_risukkuma.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_risukkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_risukkuma.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_risukkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_risukkuma_background.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_risukkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_risukkuma_background.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_risukkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_risukkuma_foreground.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_risukkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_risukkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_risukkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_risukkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_risukkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_risukkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_risukkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_risukkuma_round.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_risukkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_risukkuma_round.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_risukkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_tanukkuma.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_tanukkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_tanukkuma.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_tanukkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_tanukkuma_background.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_tanukkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_tanukkuma_background.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_tanukkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_tanukkuma_foreground.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_tanukkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_tanukkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_tanukkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_tanukkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_tanukkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_tanukkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_tanukkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_tanukkuma_round.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_tanukkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_tanukkuma_round.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_tanukkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_yetikkuma.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_yetikkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_yetikkuma.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_yetikkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_yetikkuma_background.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_yetikkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_yetikkuma_background.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_yetikkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_yetikkuma_foreground.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_yetikkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_yetikkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_yetikkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_yetikkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_yetikkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_yetikkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_yetikkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_yetikkuma_round.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_yetikkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_yetikkuma_round.webp rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_yetikkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_aoharukkuma.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_aoharukkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_aoharukkuma.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_aoharukkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_aoharukkuma_background.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_aoharukkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_aoharukkuma_background.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_aoharukkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_aoharukkuma_foreground.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_aoharukkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_aoharukkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_aoharukkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_aoharukkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_aoharukkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_aoharukkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_aoharukkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_aoharukkuma_round.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_aoharukkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_aoharukkuma_round.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_aoharukkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_background.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_background.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_doragokkuma.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_doragokkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_doragokkuma.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_doragokkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_doragokkuma_background.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_doragokkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_doragokkuma_background.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_doragokkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_doragokkuma_foreground.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_doragokkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_doragokkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_doragokkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_doragokkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_doragokkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_doragokkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_doragokkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_doragokkuma_round.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_doragokkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_doragokkuma_round.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_doragokkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_enjekkuma.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_enjekkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_enjekkuma.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_enjekkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_enjekkuma_background.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_enjekkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_enjekkuma_background.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_enjekkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_enjekkuma_foreground.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_enjekkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_enjekkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_enjekkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_enjekkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_enjekkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_enjekkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_enjekkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_enjekkuma_round.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_enjekkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_enjekkuma_round.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_enjekkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_foreground.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_higekkuma.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_higekkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_higekkuma.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_higekkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_higekkuma_background.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_higekkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_higekkuma_background.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_higekkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_higekkuma_foreground.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_higekkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_higekkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_higekkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_higekkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_higekkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_higekkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_higekkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_higekkuma_round.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_higekkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_higekkuma_round.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_higekkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_kurobarakkuma.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_kurobarakkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_kurobarakkuma.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_kurobarakkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_kurobarakkuma_background.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_kurobarakkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_kurobarakkuma_background.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_kurobarakkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_kurobarakkuma_foreground.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_kurobarakkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_kurobarakkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_kurobarakkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_kurobarakkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_kurobarakkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_kurobarakkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_kurobarakkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_kurobarakkuma_round.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_kurobarakkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_kurobarakkuma_round.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_kurobarakkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_lemonkkuma.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_lemonkkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_lemonkkuma.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_lemonkkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_lemonkkuma_background.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_lemonkkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_lemonkkuma_background.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_lemonkkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_lemonkkuma_foreground.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_lemonkkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_lemonkkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_lemonkkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_lemonkkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_lemonkkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_lemonkkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_lemonkkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_lemonkkuma_round.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_lemonkkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_lemonkkuma_round.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_lemonkkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_limekkuma.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_limekkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_limekkuma.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_limekkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_limekkuma_background.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_limekkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_limekkuma_background.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_limekkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_limekkuma_foreground.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_limekkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_limekkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_limekkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_limekkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_limekkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_limekkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_limekkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_limekkuma_round.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_limekkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_limekkuma_round.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_limekkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_meganekkuma.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_meganekkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_meganekkuma.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_meganekkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_meganekkuma_background.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_meganekkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_meganekkuma_background.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_meganekkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_meganekkuma_foreground.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_meganekkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_meganekkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_meganekkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_meganekkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_meganekkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_meganekkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_meganekkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_meganekkuma_round.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_meganekkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_meganekkuma_round.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_meganekkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_mitsuyoshikkuma.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_mitsuyoshikkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_mitsuyoshikkuma.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_mitsuyoshikkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_mitsuyoshikkuma_background.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_mitsuyoshikkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_mitsuyoshikkuma_background.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_mitsuyoshikkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_mitsuyoshikkuma_foreground.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_mitsuyoshikkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_mitsuyoshikkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_mitsuyoshikkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_mitsuyoshikkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_mitsuyoshikkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_mitsuyoshikkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_mitsuyoshikkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_mitsuyoshikkuma_round.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_mitsuyoshikkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_mitsuyoshikkuma_round.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_mitsuyoshikkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_monochrome.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_monochrome.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_ninjakkuma.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_ninjakkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_ninjakkuma.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_ninjakkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_ninjakkuma_background.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_ninjakkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_ninjakkuma_background.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_ninjakkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_ninjakkuma_foreground.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_ninjakkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_ninjakkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_ninjakkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_ninjakkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_ninjakkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_ninjakkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_ninjakkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_ninjakkuma_round.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_ninjakkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_ninjakkuma_round.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_ninjakkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_onemukkuma.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_onemukkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_onemukkuma.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_onemukkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_onemukkuma_background.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_onemukkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_onemukkuma_background.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_onemukkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_onemukkuma_foreground.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_onemukkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_onemukkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_onemukkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_onemukkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_onemukkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_onemukkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_onemukkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_onemukkuma_round.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_onemukkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_onemukkuma_round.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_onemukkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_pandakkuma.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_pandakkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_pandakkuma.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_pandakkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_pandakkuma_background.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_pandakkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_pandakkuma_background.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_pandakkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_pandakkuma_foreground.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_pandakkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_pandakkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_pandakkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_pandakkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_pandakkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_pandakkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_pandakkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_pandakkuma_round.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_pandakkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_pandakkuma_round.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_pandakkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_risukkuma.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_risukkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_risukkuma.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_risukkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_risukkuma_background.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_risukkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_risukkuma_background.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_risukkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_risukkuma_foreground.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_risukkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_risukkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_risukkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_risukkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_risukkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_risukkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_risukkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_risukkuma_round.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_risukkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_risukkuma_round.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_risukkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_tanukkuma.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_tanukkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_tanukkuma.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_tanukkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_tanukkuma_background.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_tanukkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_tanukkuma_background.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_tanukkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_tanukkuma_foreground.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_tanukkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_tanukkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_tanukkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_tanukkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_tanukkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_tanukkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_tanukkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_tanukkuma_round.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_tanukkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_tanukkuma_round.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_tanukkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_yetikkuma.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_yetikkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_yetikkuma.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_yetikkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_yetikkuma_background.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_yetikkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_yetikkuma_background.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_yetikkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_yetikkuma_foreground.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_yetikkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_yetikkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_yetikkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_yetikkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_yetikkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_yetikkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_yetikkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_yetikkuma_round.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_yetikkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_yetikkuma_round.webp rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_yetikkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_aoharukkuma.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_aoharukkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_aoharukkuma.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_aoharukkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_aoharukkuma_background.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_aoharukkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_aoharukkuma_background.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_aoharukkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_aoharukkuma_foreground.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_aoharukkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_aoharukkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_aoharukkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_aoharukkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_aoharukkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_aoharukkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_aoharukkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_aoharukkuma_round.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_aoharukkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_aoharukkuma_round.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_aoharukkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_background.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_background.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_doragokkuma.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_doragokkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_doragokkuma.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_doragokkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_doragokkuma_background.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_doragokkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_doragokkuma_background.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_doragokkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_doragokkuma_foreground.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_doragokkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_doragokkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_doragokkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_doragokkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_doragokkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_doragokkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_doragokkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_doragokkuma_round.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_doragokkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_doragokkuma_round.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_doragokkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_enjekkuma.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_enjekkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_enjekkuma.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_enjekkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_enjekkuma_background.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_enjekkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_enjekkuma_background.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_enjekkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_enjekkuma_foreground.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_enjekkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_enjekkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_enjekkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_enjekkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_enjekkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_enjekkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_enjekkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_enjekkuma_round.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_enjekkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_enjekkuma_round.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_enjekkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_foreground.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_higekkuma.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_higekkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_higekkuma.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_higekkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_higekkuma_background.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_higekkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_higekkuma_background.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_higekkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_higekkuma_foreground.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_higekkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_higekkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_higekkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_higekkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_higekkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_higekkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_higekkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_higekkuma_round.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_higekkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_higekkuma_round.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_higekkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_kurobarakkuma.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_kurobarakkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_kurobarakkuma.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_kurobarakkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_kurobarakkuma_background.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_kurobarakkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_kurobarakkuma_background.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_kurobarakkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_kurobarakkuma_foreground.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_kurobarakkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_kurobarakkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_kurobarakkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_kurobarakkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_kurobarakkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_kurobarakkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_kurobarakkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_kurobarakkuma_round.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_kurobarakkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_kurobarakkuma_round.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_kurobarakkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_lemonkkuma.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_lemonkkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_lemonkkuma.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_lemonkkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_lemonkkuma_background.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_lemonkkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_lemonkkuma_background.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_lemonkkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_lemonkkuma_foreground.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_lemonkkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_lemonkkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_lemonkkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_lemonkkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_lemonkkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_lemonkkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_lemonkkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_lemonkkuma_round.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_lemonkkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_lemonkkuma_round.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_lemonkkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_limekkuma.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_limekkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_limekkuma.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_limekkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_limekkuma_background.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_limekkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_limekkuma_background.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_limekkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_limekkuma_foreground.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_limekkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_limekkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_limekkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_limekkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_limekkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_limekkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_limekkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_limekkuma_round.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_limekkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_limekkuma_round.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_limekkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_meganekkuma.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_meganekkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_meganekkuma.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_meganekkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_meganekkuma_background.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_meganekkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_meganekkuma_background.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_meganekkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_meganekkuma_foreground.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_meganekkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_meganekkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_meganekkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_meganekkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_meganekkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_meganekkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_meganekkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_meganekkuma_round.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_meganekkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_meganekkuma_round.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_meganekkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_mitsuyoshikkuma.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_mitsuyoshikkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_mitsuyoshikkuma.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_mitsuyoshikkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_mitsuyoshikkuma_background.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_mitsuyoshikkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_mitsuyoshikkuma_background.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_mitsuyoshikkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_mitsuyoshikkuma_foreground.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_mitsuyoshikkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_mitsuyoshikkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_mitsuyoshikkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_mitsuyoshikkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_mitsuyoshikkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_mitsuyoshikkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_mitsuyoshikkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_mitsuyoshikkuma_round.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_mitsuyoshikkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_mitsuyoshikkuma_round.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_mitsuyoshikkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_monochrome.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_monochrome.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_ninjakkuma.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_ninjakkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_ninjakkuma.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_ninjakkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_ninjakkuma_background.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_ninjakkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_ninjakkuma_background.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_ninjakkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_ninjakkuma_foreground.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_ninjakkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_ninjakkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_ninjakkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_ninjakkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_ninjakkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_ninjakkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_ninjakkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_ninjakkuma_round.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_ninjakkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_ninjakkuma_round.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_ninjakkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_onemukkuma.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_onemukkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_onemukkuma.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_onemukkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_onemukkuma_background.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_onemukkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_onemukkuma_background.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_onemukkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_onemukkuma_foreground.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_onemukkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_onemukkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_onemukkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_onemukkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_onemukkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_onemukkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_onemukkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_onemukkuma_round.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_onemukkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_onemukkuma_round.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_onemukkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_pandakkuma.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_pandakkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_pandakkuma.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_pandakkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_pandakkuma_background.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_pandakkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_pandakkuma_background.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_pandakkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_pandakkuma_foreground.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_pandakkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_pandakkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_pandakkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_pandakkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_pandakkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_pandakkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_pandakkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_pandakkuma_round.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_pandakkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_pandakkuma_round.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_pandakkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_risukkuma.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_risukkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_risukkuma.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_risukkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_risukkuma_background.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_risukkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_risukkuma_background.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_risukkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_risukkuma_foreground.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_risukkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_risukkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_risukkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_risukkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_risukkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_risukkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_risukkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_risukkuma_round.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_risukkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_risukkuma_round.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_risukkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_tanukkuma.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_tanukkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_tanukkuma.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_tanukkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_tanukkuma_background.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_tanukkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_tanukkuma_background.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_tanukkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_tanukkuma_foreground.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_tanukkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_tanukkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_tanukkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_tanukkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_tanukkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_tanukkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_tanukkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_tanukkuma_round.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_tanukkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_tanukkuma_round.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_tanukkuma_round.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_yetikkuma.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_yetikkuma.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_yetikkuma.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_yetikkuma.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_yetikkuma_background.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_yetikkuma_background.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_yetikkuma_background.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_yetikkuma_background.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_yetikkuma_foreground.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_yetikkuma_foreground.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_yetikkuma_foreground.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_yetikkuma_foreground.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_yetikkuma_monochrome.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_yetikkuma_monochrome.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_yetikkuma_monochrome.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_yetikkuma_monochrome.webp diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_yetikkuma_round.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_yetikkuma_round.webp similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_yetikkuma_round.webp rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_yetikkuma_round.webp diff --git a/composeApp/src/androidMain/res/mono.png b/androidApp/src/main/res/mono.png similarity index 100% rename from composeApp/src/androidMain/res/mono.png rename to androidApp/src/main/res/mono.png diff --git a/composeApp/src/androidMain/res/values/strings.xml b/androidApp/src/main/res/values/strings.xml similarity index 100% rename from composeApp/src/androidMain/res/values/strings.xml rename to androidApp/src/main/res/values/strings.xml diff --git a/build.gradle.kts b/build.gradle.kts index 82591de..1d987cc 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,4 +4,5 @@ plugins { alias(libs.plugins.compose.multiplatform) apply false alias(libs.plugins.compose.compiler) apply false alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.multiplatform.library) apply false } diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index d5fde50..3aaf91f 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -1,54 +1,18 @@ import org.gradle.api.tasks.Copy import org.gradle.api.tasks.Delete import org.jetbrains.kotlin.gradle.dsl.JvmTarget -import org.gradle.api.file.RegularFileProperty -import java.util.Properties plugins { alias(libs.plugins.kotlin.multiplatform) alias(libs.plugins.kotlin.serialization) alias(libs.plugins.compose.multiplatform) alias(libs.plugins.compose.compiler) - alias(libs.plugins.android.application) + alias(libs.plugins.android.multiplatform.library) alias(libs.plugins.sqldelight) id("io.github.tarasovvp.kmp-secrets-plugin") version "1.3.0" id("com.diffplug.spotless") version "8.2.1" } -val localProperties = - Properties().apply { - val file = rootProject.file("local.properties") - if (file.isFile) { - file.inputStream().use(::load) - } - } - -fun signingProperty(name: String): String? = - providers.gradleProperty(name).orNull - ?: providers.environmentVariable(name).orNull - ?: localProperties.getProperty(name) - -val derakkumaKeystoreFile = rootProject.file("derakkuma.keystore") -val derakkumaKeystorePassword = signingProperty("KEYSTORE_PASSWORD") -val derakkumaKeyAlias = signingProperty("KEY_ALIAS") ?: "derakkuma" -val derakkumaKeyPassword = signingProperty("KEY_PASSWORD") ?: derakkumaKeystorePassword -val requestedReleaseBuild = - gradle.startParameter.taskNames.any { taskName -> - taskName.contains("Release", ignoreCase = true) - } - -if (requestedReleaseBuild) { - require(derakkumaKeystoreFile.isFile) { - "Release builds must be signed with ${derakkumaKeystoreFile.path}" - } - require(!derakkumaKeystorePassword.isNullOrBlank()) { - "Set KEYSTORE_PASSWORD in local.properties, Gradle properties, or the environment" - } - require(!derakkumaKeyPassword.isNullOrBlank()) { - "Set KEY_PASSWORD in local.properties, Gradle properties, or the environment" - } -} - spotless { kotlin { target("**/*.kt") @@ -62,7 +26,15 @@ spotless { } kotlin { - androidTarget { + androidLibrary { + namespace = "com.derakkuma.library" + compileSdk = 37 + minSdk = 26 + + androidResources { + enable = true + } + compilerOptions { jvmTarget.set(JvmTarget.JVM_17) } @@ -89,7 +61,7 @@ kotlin { commonMain.dependencies { implementation(compose.runtime) implementation(compose.foundation) - implementation(compose.material3) + implementation("org.jetbrains.compose.material3:material3:1.12.0-alpha01") implementation(compose.ui) implementation(compose.components.resources) implementation("org.jetbrains.compose.material:material-icons-extended:1.7.3") @@ -120,8 +92,8 @@ kotlin { } androidMain.dependencies { - implementation("androidx.browser:browser:1.8.0") - implementation("androidx.core:core-ktx:1.17.0") + implementation(libs.androidx.browser) + implementation(libs.androidx.core.ktx) implementation(libs.ktor.client.okhttp) implementation(libs.sqldelight.android) implementation(libs.androidx.activity.compose) @@ -129,10 +101,6 @@ kotlin { implementation(libs.crypto.jdk) } - androidUnitTest.dependencies { - implementation(kotlin("test")) - } - iosMain.dependencies { implementation(libs.ktor.client.darwin) implementation(libs.sqldelight.native) @@ -141,53 +109,6 @@ kotlin { } } -android { - namespace = "com.derakkuma" - compileSdk = 36 - - signingConfigs { - create("release") { - storeFile = derakkumaKeystoreFile - storePassword = derakkumaKeystorePassword.orEmpty() - keyAlias = derakkumaKeyAlias - keyPassword = derakkumaKeyPassword.orEmpty() - } - } - - defaultConfig { - applicationId = "com.derakkuma" - minSdk = 26 - targetSdk = 36 - versionCode = 2 - versionName = "0.1.1" - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - buildFeatures { - compose = true - } - - buildTypes { - debug { - applicationIdSuffix = ".debug" - isDebuggable = true - } - release { - signingConfig = signingConfigs.getByName("release") - isMinifyEnabled = true - isShrinkResources = false - proguardFiles( - getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro", - ) - } - } -} - sqldelight { databases { create("DerakkumaDb") { @@ -226,15 +147,6 @@ val deleteStaleAppIconPreviewResources = delete(staleAppIconPreviewResourceTree()) } -val emptyProguardMapFile = layout.buildDirectory.file("outputs/mapping/release/empty-proguard.map") -val writeEmptyProguardMap = - tasks.register("writeEmptyProguardMap") { - outputs.file(emptyProguardMapFile) - doLast { - emptyProguardMapFile.get().asFile.writeText("") - } - } - tasks.configureEach { val taskName = name val copiesComposeResourcesToAndroid = taskName.matches(Regex("copy.*ComposeResourcesToAndroidAssets")) @@ -255,12 +167,3 @@ tasks.configureEach { dependsOn(deleteStaleAppIconPreviewResources) } } - -tasks.matching { it.name == "packageReleaseBundle" }.configureEach { - dependsOn(writeEmptyProguardMap) - val mappingFileProperty = - javaClass.methods - .firstOrNull { it.name == "getObsfuscationMappingFile" } - ?.invoke(this) as? RegularFileProperty - mappingFileProperty?.set(emptyProguardMapFile) -} diff --git a/composeApp/src/androidMain/kotlin/com/derakkuma/appicon/AppIcon.android.kt b/composeApp/src/androidMain/kotlin/com/derakkuma/appicon/AppIcon.android.kt index 620c775..831639e 100644 --- a/composeApp/src/androidMain/kotlin/com/derakkuma/appicon/AppIcon.android.kt +++ b/composeApp/src/androidMain/kotlin/com/derakkuma/appicon/AppIcon.android.kt @@ -24,12 +24,11 @@ actual fun platformMonochromeAppIconColors(): Pair { return Color(0xFFEADDFF) to Color(0xFF4F378B) } -actual suspend fun loadAppIconPreviewAsset(path: String): ImageBitmap? = - runCatching { - DerakkumaApp.context.assets.open("app-icon-assets/$path").use { stream -> - stream.readBytes().decodeToImageBitmap() - } - }.getOrNull() +actual suspend fun loadAppIconPreviewAsset(path: String): ImageBitmap? = runCatching { + DerakkumaApp.context.assets.open("app-icon-assets/$path").use { stream -> + stream.readBytes().decodeToImageBitmap() + } +}.getOrNull() actual fun setAppIcon(option: AppIconOption): Boolean { val context = DerakkumaApp.context diff --git a/composeApp/src/androidMain/kotlin/com/derakkuma/ui/util/QrLogoImageRequest.android.kt b/composeApp/src/androidMain/kotlin/com/derakkuma/ui/util/QrLogoImageRequest.android.kt index a151a9c..b546a3d 100644 --- a/composeApp/src/androidMain/kotlin/com/derakkuma/ui/util/QrLogoImageRequest.android.kt +++ b/composeApp/src/androidMain/kotlin/com/derakkuma/ui/util/QrLogoImageRequest.android.kt @@ -3,5 +3,4 @@ package com.derakkuma.ui.util import coil3.request.ImageRequest import coil3.request.allowHardware -actual fun ImageRequest.Builder.configureQrLogoRequest(): ImageRequest.Builder = - allowHardware(false) +actual fun ImageRequest.Builder.configureQrLogoRequest(): ImageRequest.Builder = allowHardware(false) diff --git a/composeApp/src/commonMain/kotlin/com/derakkuma/App.kt b/composeApp/src/commonMain/kotlin/com/derakkuma/App.kt index 2e8a99b..bc9877f 100644 --- a/composeApp/src/commonMain/kotlin/com/derakkuma/App.kt +++ b/composeApp/src/commonMain/kotlin/com/derakkuma/App.kt @@ -1,5 +1,14 @@ package com.derakkuma +import com.derakkuma.ui.components.ExpressiveLoadingIndicator +import com.derakkuma.ui.components.ExpressiveBadgedBox +import com.derakkuma.ui.components.ExpressiveBadge +import com.derakkuma.ui.components.ExpressiveScaffold +import com.derakkuma.ui.components.expressiveNavigationBarItemColors +import com.derakkuma.ui.components.ExpressiveNavigationBarItem +import com.derakkuma.ui.components.ExpressiveNavigationBar +import com.derakkuma.ui.components.ExpressiveTopLoadingIndicator +import com.derakkuma.ui.components.expressiveScreenBackground import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.infiniteRepeatable @@ -61,10 +70,9 @@ import com.derakkuma.ui.images.BundledImage import com.derakkuma.ui.images.MaimaiAssets import com.derakkuma.ui.images.UiIconPreloadImages import com.derakkuma.ui.tabs.* -import com.derakkuma.ui.theme.DarkText import com.derakkuma.ui.theme.DerakkumaTheme -import com.derakkuma.ui.theme.MaimaiBlue -import com.derakkuma.ui.theme.MutedText +import com.derakkuma.ui.theme.ExpressiveBubblegum +import com.derakkuma.ui.theme.ExpressiveLemon import com.swmansion.kmpmaps.core.MapConfiguration import kotlinx.coroutines.launch import kotlinx.serialization.Serializable @@ -220,7 +228,7 @@ fun App(authStore: AuthStore, cacheStore: CacheStore, playStore: PlayStore) { DerakkumaTheme { if (checkingAuth || !uiIconsPreloaded) { Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - CircularProgressIndicator(color = MaterialTheme.colorScheme.onBackground) + ExpressiveLoadingIndicator(color = MaterialTheme.colorScheme.onBackground) } return@DerakkumaTheme } @@ -257,7 +265,7 @@ fun App(authStore: AuthStore, cacheStore: CacheStore, playStore: PlayStore) { ) } else if (refreshingSession) { Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - CircularProgressIndicator(color = MaterialTheme.colorScheme.onBackground) + ExpressiveLoadingIndicator(color = MaterialTheme.colorScheme.onBackground) } } else { val cookie = auth!!.cookie @@ -328,15 +336,17 @@ fun App(authStore: AuthStore, cacheStore: CacheStore, playStore: PlayStore) { } } - Scaffold( + ExpressiveScaffold( bottomBar = { Box { - NavigationBar( + val navigationContentColor = MaterialTheme.colorScheme.onPrimary + ExpressiveNavigationBar( containerColor = Color(0xFF7CBC29), - contentColor = Color.White, + contentColor = navigationContentColor, ) { Tab.entries.forEach { tab -> - NavigationBarItem( + val selectedContainer = navigationContentColor.copy(alpha = if (selectedTab == tab) 0.38f else 0.18f) + ExpressiveNavigationBarItem( selected = selectedTab == tab, onClick = { navController.navigate(tab.route) { @@ -349,10 +359,10 @@ fun App(authStore: AuthStore, cacheStore: CacheStore, playStore: PlayStore) { }, label = { Text(tab.label) }, icon = { - BadgedBox( + ExpressiveBadgedBox( badge = { if (tab == Tab.Friends && pendingFriendRequestCount > 0) { - Badge(containerColor = Color(0xFFFB4B78), contentColor = Color.White) { + ExpressiveBadge { Text(pendingFriendRequestCount.toString()) } } @@ -372,17 +382,14 @@ fun App(authStore: AuthStore, cacheStore: CacheStore, playStore: PlayStore) { imageVector = tab.flatIcon, contentDescription = tab.label, modifier = Modifier.size(26.dp).alpha(if (selectedTab == tab) 1f else 0.6f), - tint = Color.White, + tint = navigationContentColor, ) } } }, - colors = NavigationBarItemDefaults.colors( - indicatorColor = Color.White.copy(alpha = 0.2f), - selectedIconColor = Color.White, - selectedTextColor = Color.White, - unselectedIconColor = Color.White.copy(alpha = 0.6f), - unselectedTextColor = Color.White.copy(alpha = 0.6f), + colors = expressiveNavigationBarItemColors( + contentColor = navigationContentColor, + selectedContainerColor = selectedContainer, ), ) } @@ -391,11 +398,13 @@ fun App(authStore: AuthStore, cacheStore: CacheStore, playStore: PlayStore) { } }, ) { padding -> - Surface( - modifier = Modifier.fillMaxSize().padding(padding), - color = MaterialTheme.colorScheme.background, + Box( + modifier = Modifier.fillMaxSize().padding(padding).expressiveScreenBackground(), ) { Column(Modifier.fillMaxSize()) { + if (refreshingSession) { + ExpressiveTopLoadingIndicator(color = MaterialTheme.colorScheme.primary) + } if (maintenanceMode) { MaintenanceBanner() } diff --git a/composeApp/src/commonMain/kotlin/com/derakkuma/atproto/AtProtoSettings.kt b/composeApp/src/commonMain/kotlin/com/derakkuma/atproto/AtProtoSettings.kt index aafab55..0314979 100644 --- a/composeApp/src/commonMain/kotlin/com/derakkuma/atproto/AtProtoSettings.kt +++ b/composeApp/src/commonMain/kotlin/com/derakkuma/atproto/AtProtoSettings.kt @@ -1,9 +1,19 @@ package com.derakkuma.atproto +import com.derakkuma.ui.components.ExpressiveButton +import com.derakkuma.ui.components.ExpressiveGlassCard +import com.derakkuma.ui.components.ExpressiveHorizontalDivider +import com.derakkuma.ui.components.ExpressiveLinearProgressIndicator +import com.derakkuma.ui.components.ExpressiveOutlinedButton +import com.derakkuma.ui.components.ExpressiveSwitch +import com.derakkuma.ui.components.ExpressiveTextButton +import com.derakkuma.ui.components.ExpressiveAlertDialog +import com.derakkuma.ui.components.ExpressiveDropdownMenu +import com.derakkuma.ui.components.ExpressiveDropdownMenuItem +import com.derakkuma.ui.components.ExpressiveTextField import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.CloudSync @@ -13,6 +23,7 @@ import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.font.FontWeight @@ -215,10 +226,10 @@ fun AtProtoSection( verticalAlignment = Alignment.CenterVertically, ) { Column(modifier = Modifier.weight(1f)) { - Text("Linked Account", fontSize = 13.sp, fontWeight = FontWeight.Medium, color = DarkText) - Text("@${account!!.handle}", fontSize = 12.sp, color = MutedText) + Text("Linked Account", fontSize = 13.sp, fontWeight = FontWeight.Medium, color = MaterialTheme.colorScheme.onSurface) + Text("@${account!!.handle}", fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } - OutlinedButton( + ExpressiveOutlinedButton( onClick = { scope.launch { try { @@ -232,8 +243,7 @@ fun AtProtoSection( } } }, - shape = RoundedCornerShape(8.dp), - colors = ButtonDefaults.outlinedButtonColors(contentColor = DangerRed), + colors = ButtonDefaults.outlinedButtonColors(contentColor = MaterialTheme.colorScheme.error), ) { Icon(Icons.Default.LinkOff, contentDescription = null, modifier = Modifier.size(16.dp)) Spacer(Modifier.width(4.dp)) @@ -241,13 +251,12 @@ fun AtProtoSection( } } - if (showConfig) HorizontalDivider(color = MutedText.copy(alpha = 0.12f)) + if (showConfig) ExpressiveHorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.6f)) if (hasSynced) { - OutlinedButton( + ExpressiveOutlinedButton( onClick = { configExpanded = !configExpanded }, modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), ) { Text(if (configExpanded) "Hide configuration" else "Configure", fontSize = 13.sp) } @@ -255,8 +264,8 @@ fun AtProtoSection( // Publish toggles if (showConfig) { - Text("Publish to AT Protocol", fontSize = 13.sp, fontWeight = FontWeight.Medium, color = DarkText) - Text("Control what data gets published to your PDS", fontSize = 11.sp, color = MutedText) + Text("Publish to AT Protocol", fontSize = 13.sp, fontWeight = FontWeight.Medium, color = MaterialTheme.colorScheme.onSurface) + Text("Control what data gets published to your PDS", fontSize = 11.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) PublishToggle("Profile", "Player name, rating, images, comment", settings.publishProfile) { checked -> settings = settings.copy(publishProfile = checked, publishFriendCode = if (checked) settings.publishFriendCode else false) @@ -302,17 +311,17 @@ fun AtProtoSection( configureAtProtoBackgroundSync(account != null && checked) } if (settings.backgroundSyncEnabled && !canScheduleAtProtoBackgroundSync()) { - Text("Android needs exact alarm permission for reliable 12-hour background sync.", fontSize = 11.sp, color = MutedText) - TextButton(onClick = { openAtProtoBackgroundSyncSettings() }) { + Text("Android needs exact alarm permission for reliable 12-hour background sync.", fontSize = 11.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) + ExpressiveTextButton(onClick = { openAtProtoBackgroundSyncSettings() }) { Text("Open alarm permission settings", fontSize = 12.sp) } } } - if (showConfig) HorizontalDivider(color = MutedText.copy(alpha = 0.12f)) + if (showConfig) ExpressiveHorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.6f)) // Manual sync button - Button( + ExpressiveButton( onClick = { scope.launch { syncing = true @@ -346,8 +355,7 @@ fun AtProtoSection( } }, modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = ButtonDefaults.buttonColors(containerColor = PrimaryBlue), + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary), enabled = !syncing, ) { Icon(Icons.Default.CloudSync, contentDescription = null, modifier = Modifier.size(18.dp)) @@ -362,32 +370,32 @@ fun AtProtoSection( animationSpec = tween(durationMillis = 450), label = "atproto-sync-progress", ) - LinearProgressIndicator( + ExpressiveLinearProgressIndicator( progress = { animatedProgress }, modifier = Modifier.fillMaxWidth(), - color = PrimaryBlue, + color = MaterialTheme.colorScheme.primary, ) - Text(syncProgress!!.status, fontSize = 11.sp, color = MutedText) - Text("Don't close the app while syncing!", fontSize = 11.sp, color = DangerRed) + Text(syncProgress!!.status, fontSize = 11.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) + Text("Don't close the app while syncing!", fontSize = 11.sp, color = MaterialTheme.colorScheme.error) } // Sync status if (showConfig && syncState.lastSyncTime.isNotEmpty()) { - Text("Last synced: ${syncState.lastSyncTime.take(19)}", fontSize = 11.sp, color = MutedText) + Text("Last synced: ${syncState.lastSyncTime.take(19)}", fontSize = 11.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) val plays = syncState.publishedPlayCount val bests = syncState.publishedBestCount val friends = syncState.publishedFriendCount val favorites = syncState.publishedFavoriteSongCount val circles = syncState.publishedCircleRecordCount if (plays > 0 || bests > 0 || friends > 0 || favorites > 0 || circles > 0) { - Text("Total published: $plays plays, $bests bests, $friends friends, $favorites favorites, $circles circle records", fontSize = 11.sp, color = MutedText) + Text("Total published: $plays plays, $bests bests, $friends friends, $favorites favorites, $circles circle records", fontSize = 11.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } val queued = cacheStore.getPendingAtProtoWrites().size if (queued > 0) { - Text("$queued write${if (queued == 1) "" else "s"} queued for retry", fontSize = 11.sp, color = MutedText) + Text("$queued write${if (queued == 1) "" else "s"} queued for retry", fontSize = 11.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } if (syncState.lastError.isNotBlank()) { - Text("Last ATProto error: ${syncState.lastError}", fontSize = 11.sp, color = DangerRed) + Text("Last ATProto error: ${syncState.lastError}", fontSize = 11.sp, color = MaterialTheme.colorScheme.error) } } } @@ -431,23 +439,26 @@ fun AtProtoSection( } else { // Not linked SectionCard { - Text("Link your Bluesky/ATProtocol account to publish your plays on the Atmosphere and connect with friends!", fontSize = 12.sp, color = MutedText) + Text("Link your Bluesky/ATProtocol account to publish your plays on the Atmosphere and connect with friends!", fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) Box(modifier = Modifier.fillMaxWidth()) { - OutlinedTextField( + ExpressiveTextField( value = handleOrDid, onValueChange = { handleOrDid = it }, modifier = Modifier.fillMaxWidth(), singleLine = true, label = { Text("Handle or DID") }, placeholder = { Text("you.bsky.social") }, - ) - DropdownMenu( + ) + ExpressiveDropdownMenu( expanded = handleSuggestions.isNotEmpty(), onDismissRequest = { handleSuggestions = emptyList() }, modifier = Modifier.fillMaxWidth(0.92f), ) { - handleSuggestions.forEach { suggestion -> - DropdownMenuItem( + handleSuggestions.forEachIndexed { index, suggestion -> + ExpressiveDropdownMenuItem( + selected = false, + index = index, + lastIndex = handleSuggestions.lastIndex, text = { Row( verticalAlignment = Alignment.CenterVertically, @@ -456,14 +467,14 @@ fun AtProtoSection( if (suggestion.avatar.isNotBlank()) { MaimaiRemoteImage( url = suggestion.avatar, - modifier = Modifier.size(34.dp).clip(CircleShape), + modifier = Modifier.size(34.dp).clip(RoundedCornerShape(2.dp)), contentDescription = suggestion.displayName.ifBlank { suggestion.handle }, contentScale = ContentScale.Crop, ) } Column { Text("@${suggestion.handle}", fontSize = 13.sp, fontWeight = FontWeight.SemiBold) - if (suggestion.displayName.isNotBlank()) Text(suggestion.displayName, fontSize = 11.sp, color = MutedText) + if (suggestion.displayName.isNotBlank()) Text(suggestion.displayName, fontSize = 11.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } } }, @@ -475,7 +486,7 @@ fun AtProtoSection( } } } - Button( + ExpressiveButton( onClick = { scope.launch { linking = true @@ -499,8 +510,7 @@ fun AtProtoSection( } }, modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = ButtonDefaults.buttonColors(containerColor = PrimaryBlue), + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary), enabled = !linking, ) { Icon(Icons.Default.Link, contentDescription = null, modifier = Modifier.size(18.dp)) @@ -512,13 +522,13 @@ fun AtProtoSection( snackMsg?.let { SectionCard(contentPadding = 12.dp) { - Text(it, fontSize = 12.sp, color = if (it.startsWith("Error:")) DangerRed else MutedText) + Text(it, fontSize = 12.sp, color = if (it.startsWith("Error:")) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.onSurfaceVariant) } } } else { // Platform not supported (iOS stub) SectionCard { - Text("AT Protocol integration is unavailable in this build.", fontSize = 12.sp, color = MutedText) + Text("AT Protocol integration is unavailable in this build.", fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } } } @@ -532,12 +542,12 @@ fun AtProtoSection( } if (showFriendCodePublishConfirm) { - AlertDialog( + ExpressiveAlertDialog( onDismissRequest = { showFriendCodePublishConfirm = false }, title = { Text("Publish friend code?") }, text = { Text("This will add your maimai friend code to your public AT Protocol profile, which lets Bluesky friends discover you and send maimai friend requests through Derakkuma. Even if you remove it later, other apps or indexers may have already copied it.") }, confirmButton = { - TextButton( + ExpressiveTextButton( onClick = { settings = settings.copy(publishProfile = true, publishFriendCode = true) cacheStore.putAtProtoPublishSettings(settings) @@ -546,7 +556,7 @@ fun AtProtoSection( ) { Text("Publish") } }, dismissButton = { - TextButton(onClick = { showFriendCodePublishConfirm = false }) { Text("Cancel") } + ExpressiveTextButton(onClick = { showFriendCodePublishConfirm = false }) { Text("Cancel") } }, ) } @@ -582,10 +592,10 @@ private fun SectionCard( contentPadding: androidx.compose.ui.unit.Dp = 16.dp, content: @Composable ColumnScope.() -> Unit, ) { - Card( + ExpressiveGlassCard( modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), + cornerRadius = 24.dp, + containerColor = Color.White, ) { Column( modifier = Modifier.padding(contentPadding), @@ -602,7 +612,7 @@ private fun AtProtoActivityCard( error: String?, ) { Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { - Text("Atmosphere Activity", fontSize = 13.sp, fontWeight = FontWeight.Medium, color = DarkText) + Text("Atmosphere Activity", fontSize = 13.sp, fontWeight = FontWeight.Medium, color = MaterialTheme.colorScheme.onSurface) if (error != null) { Text(error, fontSize = 12.sp, color = MaterialTheme.colorScheme.error) return@Column @@ -614,17 +624,17 @@ private fun AtProtoActivityCard( SmallStat("Avg", if (stats.bestCount > 0) "${formatScore(stats.averageAchievement)}%" else "--") } } - Text("Record Feed", fontSize = 12.sp, fontWeight = FontWeight.SemiBold, color = DarkText) - Text("Recent records from people you follow plus everyone on the Atmosphere.", fontSize = 11.sp, color = MutedText) + Text("Record Feed", fontSize = 12.sp, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.onSurface) + Text("Recent records from people you follow plus everyone on the Atmosphere.", fontSize = 11.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) if (feed.isEmpty()) { - Text("No record activity yet... get 'em on Derakkuma! :D", fontSize = 12.sp, color = MutedText) + Text("No record activity yet... get 'em on Derakkuma! :D", fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } else { feed.take(20).forEach { play -> Column(Modifier.fillMaxWidth().padding(vertical = 2.dp)) { - Text(play.songName.ifBlank { "Unknown song" }, fontSize = 13.sp, fontWeight = FontWeight.SemiBold, color = DarkText, maxLines = 1) + Text(play.songName.ifBlank { "Unknown song" }, fontSize = 13.sp, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.onSurface, maxLines = 1) val player = play.playerName.ifBlank { play.did.take(16) + "…" } val source = play.source.takeIf { it.isNotBlank() }?.let { " • $it" }.orEmpty() - Text("$player • ${play.difficulty} ${play.level} • ${formatScore(play.achievement.toDoubleOrNull() ?: 0.0)}%$source", fontSize = 11.sp, color = MutedText, maxLines = 1) + Text("$player • ${play.difficulty} ${play.level} • ${formatScore(play.achievement.toDoubleOrNull() ?: 0.0)}%$source", fontSize = 11.sp, color = MaterialTheme.colorScheme.onSurfaceVariant, maxLines = 1) } } } @@ -634,8 +644,8 @@ private fun AtProtoActivityCard( @Composable private fun SmallStat(label: String, value: String) { Column(horizontalAlignment = Alignment.CenterHorizontally) { - Text(value, fontSize = 14.sp, fontWeight = FontWeight.Bold, color = PrimaryBlue) - Text(label, fontSize = 10.sp, color = MutedText) + Text(value, fontSize = 14.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.primary) + Text(label, fontSize = 10.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } } @@ -649,15 +659,15 @@ private fun SocialBridgeCard( onRequest: (AtProtoSocialProfile) -> Unit, ) { Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { - Text("Social Bridge", fontSize = 13.sp, fontWeight = FontWeight.Medium, color = DarkText) - Text("Discovery uses published Derakkuma profiles.", fontSize = 11.sp, color = MutedText) + Text("Social Bridge", fontSize = 13.sp, fontWeight = FontWeight.Medium, color = MaterialTheme.colorScheme.onSurface) + Text("Discovery uses published Derakkuma profiles.", fontSize = 11.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) - if (loading) LinearProgressIndicator(modifier = Modifier.fillMaxWidth(), color = PrimaryBlue) + if (loading) ExpressiveLinearProgressIndicator(modifier = Modifier.fillMaxWidth(), color = MaterialTheme.colorScheme.primary) if (error != null) Text(error, fontSize = 12.sp, color = MaterialTheme.colorScheme.error) - Text("Maimai friends on Bluesky", fontSize = 12.sp, fontWeight = FontWeight.SemiBold, color = DarkText) + Text("Maimai friends on Bluesky", fontSize = 12.sp, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.onSurface) if (maimaiFriendMatches.isEmpty()) { - Text(if (loading) "Looking for matches..." else "No maimai friends matched a published Bluesky profile yet.", fontSize = 12.sp, color = MutedText) + Text(if (loading) "Looking for matches..." else "No maimai friends matched a published Bluesky profile yet.", fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } else { maimaiFriendMatches.forEach { match -> SocialActionRow( @@ -670,9 +680,9 @@ private fun SocialBridgeCard( } } - Text("Bluesky follows on maimai", fontSize = 12.sp, fontWeight = FontWeight.SemiBold, color = DarkText) + Text("Bluesky follows on maimai", fontSize = 12.sp, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.onSurface) if (bskyFollowMatches.isEmpty()) { - Text(if (loading) "Looking for matches..." else "No Bluesky follows matched a published Derakkuma profile yet.", fontSize = 12.sp, color = MutedText) + Text(if (loading) "Looking for matches..." else "No Bluesky follows matched a published Derakkuma profile yet.", fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } else { bskyFollowMatches.forEach { match -> val label = match.profile.playerName.ifBlank { match.follow.displayName.ifBlank { match.follow.handle } } @@ -698,10 +708,10 @@ private fun SocialActionRow( ) { Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) { Column(Modifier.weight(1f)) { - Text(title, fontSize = 13.sp, fontWeight = FontWeight.SemiBold, color = DarkText, maxLines = 1) - Text(subtitle, fontSize = 11.sp, color = MutedText, maxLines = 1) + Text(title, fontSize = 13.sp, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.onSurface, maxLines = 1) + Text(subtitle, fontSize = 11.sp, color = MaterialTheme.colorScheme.onSurfaceVariant, maxLines = 1) } - OutlinedButton(onClick = onClick, enabled = enabled, shape = RoundedCornerShape(8.dp)) { + ExpressiveOutlinedButton(onClick = onClick, enabled = enabled) { Text(action, fontSize = 12.sp) } } @@ -721,14 +731,13 @@ private fun PublishToggle( verticalAlignment = Alignment.CenterVertically, ) { Column(modifier = Modifier.weight(1f)) { - Text(label, fontSize = 13.sp, fontWeight = FontWeight.Medium, color = DarkText) - Text(description, fontSize = 11.sp, color = MutedText) + Text(label, fontSize = 13.sp, fontWeight = FontWeight.Medium, color = MaterialTheme.colorScheme.onSurface) + Text(description, fontSize = 11.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } - Switch( + ExpressiveSwitch( checked = checked, onCheckedChange = onCheckedChange, - enabled = enabled, - colors = SwitchDefaults.colors(checkedTrackColor = PrimaryBlue), + enabled = enabled ) } } diff --git a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/OnboardingScreen.kt b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/OnboardingScreen.kt index 6315291..3c434a7 100644 --- a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/OnboardingScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/OnboardingScreen.kt @@ -10,7 +10,6 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material3.* @@ -26,20 +25,21 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.derakkuma.atproto.openOAuthUrl import com.derakkuma.ui.components.MaintenanceBanner +import com.derakkuma.ui.components.ExpressiveButton +import com.derakkuma.ui.components.ExpressiveGlassCard +import com.derakkuma.ui.components.ExpressiveIconSurface +import com.derakkuma.ui.components.ExpressiveCheckbox +import com.derakkuma.ui.components.ExpressiveTextButton +import com.derakkuma.ui.components.expressiveScreenBackground import com.derakkuma.ui.images.BundledIcon import com.derakkuma.ui.images.BundledImage import com.derakkuma.ui.images.DerakkumaGroupAssets -import com.derakkuma.ui.theme.CardWhite -import com.derakkuma.ui.theme.DarkText -import com.derakkuma.ui.theme.MaimaiBlue -import com.derakkuma.ui.theme.MutedText -import com.derakkuma.ui.theme.PrimaryBlue @Composable fun OnboardingScreen(onComplete: () -> Unit, maintenanceMode: Boolean = false) { var accepted by remember { mutableStateOf(false) } - Surface(color = MaimaiBlue, modifier = Modifier.fillMaxSize()) { + Box(modifier = Modifier.fillMaxSize().expressiveScreenBackground()) { Column( modifier = Modifier .fillMaxSize() @@ -53,9 +53,7 @@ fun OnboardingScreen(onComplete: () -> Unit, maintenanceMode: Boolean = false) { MaintenanceBanner() } - Card( - shape = RoundedCornerShape(28.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), + ExpressiveGlassCard( modifier = Modifier.fillMaxWidth(), ) { Column( @@ -66,10 +64,10 @@ fun OnboardingScreen(onComplete: () -> Unit, maintenanceMode: Boolean = false) { DerakkumaGroupMascots() Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(6.dp)) { - Text("Welcome to Derakkuma", color = DarkText, fontWeight = FontWeight.Bold, fontSize = 22.sp, textAlign = TextAlign.Center) + Text("Welcome to Derakkuma", color = MaterialTheme.colorScheme.onSurface, fontWeight = FontWeight.Bold, fontSize = 22.sp, textAlign = TextAlign.Center) Text( "Your maimai DX NET companion!", - color = MutedText, + color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = 14.sp, lineHeight = 19.sp, textAlign = TextAlign.Center, @@ -92,40 +90,39 @@ fun OnboardingScreen(onComplete: () -> Unit, maintenanceMode: Boolean = false) { text = "Your maimai DX NET session and cached data are stored only on this device. They never leave your device unless you explicitly enable sharing/syncing features.", ) - Card( - colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.error.copy(alpha = 0.08f)), - shape = RoundedCornerShape(18.dp), + ExpressiveGlassCard( modifier = Modifier.fillMaxWidth(), + cornerRadius = 28.dp, + containerColor = MaterialTheme.colorScheme.errorContainer.copy(alpha = 0.42f), ) { Column(Modifier.fillMaxWidth().padding(start = 12.dp, end = 12.dp, top = 8.dp, bottom = 2.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { - Text("Before we start", color = DarkText, fontWeight = FontWeight.Bold, fontSize = 12.sp) + Text("Before we start", color = MaterialTheme.colorScheme.onSurface, fontWeight = FontWeight.Bold, fontSize = 12.sp) Text( "Derakkuma is an unofficial maimai DX NET client and is not affiliated with or endorsed by SEGA. By continuing, you agree that your use of maimai DX NET through this app follows maimai DX NET Terms of Service and Privacy Policy.", - color = MutedText, + color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = 10.sp, lineHeight = 17.sp, ) Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { - TextButton(onClick = { openOAuthUrl("https://derakkuma.com/terms") }) { + ExpressiveTextButton(onClick = { openOAuthUrl("https://derakkuma.com/terms") }) { Text("Terms of Service", fontSize = 11.sp) } - TextButton(onClick = { openOAuthUrl("https://derakkuma.com/privacy") }) { + ExpressiveTextButton(onClick = { openOAuthUrl("https://derakkuma.com/privacy") }) { Text("Privacy Policy", fontSize = 11.sp) } } Row(verticalAlignment = Alignment.CenterVertically) { - Checkbox(checked = accepted, onCheckedChange = { accepted = it }) - Text("I understand and agree", color = DarkText, fontSize = 13.sp) + ExpressiveCheckbox(checked = accepted, onCheckedChange = { accepted = it }) + Text("I understand and agree", color = MaterialTheme.colorScheme.onSurface, fontSize = 13.sp) } } } - Button( + ExpressiveButton( onClick = onComplete, enabled = accepted && !maintenanceMode, - colors = ButtonDefaults.buttonColors(containerColor = PrimaryBlue), + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary), modifier = Modifier.fillMaxWidth().height(52.dp), - shape = RoundedCornerShape(16.dp), ) { Text("Get started", fontWeight = FontWeight.Bold) } @@ -203,17 +200,17 @@ private data class MascotSpec( @Composable private fun OnboardingPoint(icon: Any, title: String, text: String) { Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.Top) { - Surface(shape = CircleShape, color = PrimaryBlue.copy(alpha = 0.12f), modifier = Modifier.size(40.dp)) { + ExpressiveIconSurface(size = 40.dp, containerColor = MaterialTheme.colorScheme.primaryContainer) { Box(contentAlignment = Alignment.Center) { when (icon) { - is androidx.compose.ui.graphics.vector.ImageVector -> Icon(icon, contentDescription = null, tint = PrimaryBlue, modifier = Modifier.size(21.dp)) + is androidx.compose.ui.graphics.vector.ImageVector -> Icon(icon, contentDescription = null, tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(21.dp)) is BundledImage -> BundledIcon(icon, contentDescription = null, modifier = Modifier.size(24.dp)) } } } Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(3.dp)) { - Text(title, color = DarkText, fontWeight = FontWeight.Bold, fontSize = 14.sp) - Text(text, color = MutedText, fontSize = 12.sp, lineHeight = 17.sp) + Text(title, color = MaterialTheme.colorScheme.onSurface, fontWeight = FontWeight.Bold, fontSize = 14.sp) + Text(text, color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = 12.sp, lineHeight = 17.sp) } } } diff --git a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/ExpressiveControls.kt b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/ExpressiveControls.kt new file mode 100644 index 0000000..9ab47d1 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/ExpressiveControls.kt @@ -0,0 +1,758 @@ +package com.derakkuma.ui.components + +import androidx.compose.animation.core.AnimationSpec +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsPressedAsState +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.safeDrawing +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonColors +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.AssistChip +import androidx.compose.material3.AssistChipDefaults +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Badge +import androidx.compose.material3.BadgedBox +import androidx.compose.material3.Checkbox +import androidx.compose.material3.CheckboxDefaults +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.FilterChip +import androidx.compose.material3.FilterChipDefaults +import androidx.compose.material3.FabPosition +import androidx.compose.material3.FilledTonalIconButton +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.LinearWavyProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.MenuDefaults +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.NavigationBarItemColors +import androidx.compose.material3.NavigationBarItemDefaults +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Slider +import androidx.compose.material3.SliderDefaults +import androidx.compose.material3.Snackbar +import androidx.compose.material3.Switch +import androidx.compose.material3.SwitchDefaults +import androidx.compose.material3.TextButton +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldColors +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.material3.ToggleButton +import androidx.compose.material3.ToggleButtonShapes +import androidx.compose.material3.WavyProgressIndicatorDefaults +import androidx.compose.material3.pulltorefresh.PullToRefreshBox +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.scale +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp + +import androidx.compose.material3.SearchBar +import androidx.compose.material3.SearchBarColors +import androidx.compose.material3.SearchBarDefaults +import androidx.compose.material3.Scaffold + +@Composable +fun expressiveTextFieldColors(): TextFieldColors = TextFieldDefaults.colors( + focusedContainerColor = MaterialTheme.colorScheme.surfaceContainerHighest, + unfocusedContainerColor = MaterialTheme.colorScheme.surfaceContainerHigh, + disabledContainerColor = MaterialTheme.colorScheme.surfaceContainer, + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + disabledIndicatorColor = Color.Transparent, +) + +@Composable +fun ExpressiveTextField( + value: String, + onValueChange: (String) -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + readOnly: Boolean = false, + label: @Composable (() -> Unit)? = null, + placeholder: @Composable (() -> Unit)? = null, + leadingIcon: @Composable (() -> Unit)? = null, + trailingIcon: @Composable (() -> Unit)? = null, + supportingText: @Composable (() -> Unit)? = null, + singleLine: Boolean = false, + maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE, + visualTransformation: VisualTransformation = VisualTransformation.None, +) { + TextField( + value = value, + onValueChange = onValueChange, + modifier = modifier, + enabled = enabled, + readOnly = readOnly, + label = label, + placeholder = placeholder, + leadingIcon = leadingIcon, + trailingIcon = trailingIcon, + supportingText = supportingText, + singleLine = singleLine, + maxLines = maxLines, + visualTransformation = visualTransformation, + shape = MaterialTheme.shapes.extraLarge, + colors = expressiveTextFieldColors(), + ) +} + +object ExpressiveMotion { + val Spatial: AnimationSpec = spring( + dampingRatio = Spring.DampingRatioMediumBouncy, + stiffness = Spring.StiffnessMediumLow, + ) + + val Effects: AnimationSpec = tween(durationMillis = 220) +} + +@Composable +fun Modifier.expressivePressScale(interactionSource: MutableInteractionSource): Modifier { + val pressed by interactionSource.collectIsPressedAsState() + val scale by androidx.compose.animation.core.animateFloatAsState( + targetValue = if (pressed) 0.96f else 1f, + animationSpec = ExpressiveMotion.Spatial, + label = "expressive-press-scale", + ) + return scale(scale) +} + +@Composable +fun ExpressiveButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + colors: ButtonColors = ButtonDefaults.buttonColors(), + contentPadding: PaddingValues = ButtonDefaults.ContentPadding, + content: @Composable RowScope.() -> Unit, +) { + val interactionSource = remember { MutableInteractionSource() } + Button( + onClick = onClick, + modifier = modifier.expressivePressScale(interactionSource), + enabled = enabled, + shape = MaterialTheme.shapes.extraLarge, + colors = colors, + contentPadding = contentPadding, + interactionSource = interactionSource, + content = content, + ) +} + +@Composable +fun ExpressiveOutlinedButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + colors: ButtonColors = ButtonDefaults.outlinedButtonColors(), + contentPadding: PaddingValues = ButtonDefaults.ContentPadding, + content: @Composable RowScope.() -> Unit, +) { + val interactionSource = remember { MutableInteractionSource() } + OutlinedButton( + onClick = onClick, + modifier = modifier.expressivePressScale(interactionSource), + enabled = enabled, + shape = MaterialTheme.shapes.extraLarge, + colors = colors, + contentPadding = contentPadding, + interactionSource = interactionSource, + content = content, + ) +} + +@Composable +fun ExpressiveTextButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + contentPadding: PaddingValues = ButtonDefaults.TextButtonContentPadding, + content: @Composable RowScope.() -> Unit, +) { + val interactionSource = remember { MutableInteractionSource() } + TextButton( + onClick = onClick, + modifier = modifier.expressivePressScale(interactionSource), + enabled = enabled, + shape = MaterialTheme.shapes.extraLarge, + contentPadding = contentPadding, + interactionSource = interactionSource, + content = content, + ) +} + +@Composable +fun ExpressiveSwitch( + checked: Boolean, + onCheckedChange: ((Boolean) -> Unit)?, + modifier: Modifier = Modifier, + enabled: Boolean = true, +) { + val interactionSource = remember { MutableInteractionSource() } + Switch( + checked = checked, + onCheckedChange = onCheckedChange, + modifier = modifier.expressivePressScale(interactionSource), + enabled = enabled, + colors = SwitchDefaults.colors( + checkedTrackColor = MaterialTheme.colorScheme.primary, + checkedThumbColor = MaterialTheme.colorScheme.onPrimary, + checkedBorderColor = MaterialTheme.colorScheme.primary, + uncheckedTrackColor = MaterialTheme.colorScheme.surfaceContainerHighest, + uncheckedThumbColor = MaterialTheme.colorScheme.onSurfaceVariant, + uncheckedBorderColor = MaterialTheme.colorScheme.outlineVariant, + ), + interactionSource = interactionSource, + ) +} + +@Composable +fun ExpressiveCheckbox( + checked: Boolean, + onCheckedChange: ((Boolean) -> Unit)?, + modifier: Modifier = Modifier, + enabled: Boolean = true, +) { + val interactionSource = remember { MutableInteractionSource() } + Checkbox( + checked = checked, + onCheckedChange = onCheckedChange, + modifier = modifier.expressivePressScale(interactionSource), + enabled = enabled, + colors = CheckboxDefaults.colors( + checkedColor = MaterialTheme.colorScheme.primary, + uncheckedColor = MaterialTheme.colorScheme.outline, + checkmarkColor = MaterialTheme.colorScheme.onPrimary, + ), + interactionSource = interactionSource, + ) +} + +@Composable +fun ExpressiveFilterChip( + selected: Boolean, + onClick: () -> Unit, + label: @Composable () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + selectedColor: Color = MaterialTheme.colorScheme.primary, + shape: Shape = MaterialTheme.shapes.extraLarge, +) { + val interactionSource = remember { MutableInteractionSource() } + FilterChip( + selected = selected, + onClick = onClick, + label = label, + modifier = modifier.expressivePressScale(interactionSource), + enabled = enabled, + shape = shape, + colors = FilterChipDefaults.filterChipColors( + containerColor = Color.White, + labelColor = MaterialTheme.colorScheme.onSurface, + selectedContainerColor = selectedColor, + selectedLabelColor = MaterialTheme.colorScheme.onPrimary, + ), + border = FilterChipDefaults.filterChipBorder( + enabled = enabled, + selected = selected, + borderColor = Color.Transparent, + selectedBorderColor = selectedColor, + ), + interactionSource = interactionSource, + ) +} + +@Composable +fun ExpressiveAssistChip( + onClick: () -> Unit, + label: @Composable () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + containerColor: Color = MaterialTheme.colorScheme.secondaryContainer, + labelColor: Color = MaterialTheme.colorScheme.onSecondaryContainer, +) { + val interactionSource = remember { MutableInteractionSource() } + AssistChip( + onClick = onClick, + label = label, + modifier = modifier.expressivePressScale(interactionSource), + enabled = enabled, + shape = MaterialTheme.shapes.extraLarge, + colors = AssistChipDefaults.assistChipColors( + containerColor = containerColor, + labelColor = labelColor, + ), + border = AssistChipDefaults.assistChipBorder(enabled = enabled, borderColor = Color.Transparent), + interactionSource = interactionSource, + ) +} + +@Composable +fun ExpressiveSnackbar( + modifier: Modifier = Modifier, + action: @Composable (() -> Unit)? = null, + content: @Composable () -> Unit, +) { + Snackbar( + modifier = modifier, + shape = MaterialTheme.shapes.extraLarge, + containerColor = MaterialTheme.colorScheme.inverseSurface, + contentColor = MaterialTheme.colorScheme.inverseOnSurface, + actionContentColor = MaterialTheme.colorScheme.inversePrimary, + action = action, + content = content, + ) +} + +@Composable +fun ExpressiveFloatingActionButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, + containerColor: Color = MaterialTheme.colorScheme.primary, + contentColor: Color = MaterialTheme.colorScheme.onPrimary, + content: @Composable () -> Unit, +) { + val interactionSource = remember { MutableInteractionSource() } + FloatingActionButton( + onClick = onClick, + modifier = modifier.expressivePressScale(interactionSource), + shape = MaterialTheme.shapes.extraLarge, + containerColor = containerColor, + contentColor = contentColor, + interactionSource = interactionSource, + content = content, + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ExpressiveSearchBar( + inputField: @Composable () -> Unit, + expanded: Boolean, + onExpandedChange: (Boolean) -> Unit, + modifier: Modifier = Modifier, + colors: SearchBarColors = SearchBarDefaults.colors( + containerColor = Color.White, + dividerColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.22f), + ), + content: @Composable ColumnScope.() -> Unit = {}, +) { + SearchBar( + inputField = inputField, + expanded = expanded, + onExpandedChange = onExpandedChange, + modifier = modifier, + shape = MaterialTheme.shapes.extraLarge, + colors = colors, + content = content, + ) +} + +@Composable +fun ExpressiveDropdownMenu( + expanded: Boolean, + onDismissRequest: () -> Unit, + modifier: Modifier = Modifier, + content: @Composable ColumnScope.() -> Unit, +) { + DropdownMenu( + expanded = expanded, + onDismissRequest = onDismissRequest, + modifier = modifier, + shape = MenuDefaults.shape, + containerColor = MaterialTheme.colorScheme.surfaceContainer, + tonalElevation = 0.dp, + shadowElevation = 0.dp, + content = content, + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ExpressiveDropdownMenuItem( + selected: Boolean, + index: Int, + lastIndex: Int, + onClick: () -> Unit, + text: @Composable () -> Unit, + leadingIcon: @Composable (() -> Unit)? = null, +) { + DropdownMenuItem( + selected = selected, + text = text, + leadingIcon = leadingIcon, + shapes = MenuDefaults.itemShape(index, lastIndex), + colors = MenuDefaults.selectableItemVibrantColors(), + onClick = onClick, + ) +} + +@Composable +fun ExpressiveAlertDialog( + onDismissRequest: () -> Unit, + confirmButton: @Composable () -> Unit, + modifier: Modifier = Modifier, + dismissButton: @Composable (() -> Unit)? = null, + icon: @Composable (() -> Unit)? = null, + title: @Composable (() -> Unit)? = null, + text: @Composable (() -> Unit)? = null, +) { + AlertDialog( + onDismissRequest = onDismissRequest, + confirmButton = confirmButton, + modifier = modifier, + dismissButton = dismissButton, + icon = icon, + title = title, + text = text, + shape = MaterialTheme.shapes.extraLarge, + containerColor = Color.White, + tonalElevation = 0.dp, + ) +} + +@Composable +fun ExpressiveNavigationBar( + modifier: Modifier = Modifier, + containerColor: Color, + contentColor: Color, + tonalElevation: Dp = 10.dp, + content: @Composable RowScope.() -> Unit, +) { + NavigationBar( + modifier = modifier, + containerColor = containerColor, + contentColor = contentColor, + tonalElevation = tonalElevation, + content = content, + ) +} + +@Composable +fun expressiveNavigationBarItemColors( + contentColor: Color, + selectedContainerColor: Color, +): NavigationBarItemColors = NavigationBarItemDefaults.colors( + indicatorColor = selectedContainerColor, + selectedIconColor = contentColor, + selectedTextColor = contentColor, + unselectedIconColor = contentColor.copy(alpha = 0.6f), + unselectedTextColor = contentColor.copy(alpha = 0.6f), +) + +@Composable +fun RowScope.ExpressiveNavigationBarItem( + selected: Boolean, + onClick: () -> Unit, + icon: @Composable () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + label: @Composable (() -> Unit)? = null, + alwaysShowLabel: Boolean = true, + colors: NavigationBarItemColors, +) { + NavigationBarItem( + selected = selected, + onClick = onClick, + icon = icon, + modifier = modifier, + enabled = enabled, + label = label, + alwaysShowLabel = alwaysShowLabel, + colors = colors, + ) +} + +@Composable +fun ExpressiveScaffold( + modifier: Modifier = Modifier, + topBar: @Composable () -> Unit = {}, + bottomBar: @Composable () -> Unit = {}, + snackbarHost: @Composable () -> Unit = {}, + floatingActionButton: @Composable () -> Unit = {}, + floatingActionButtonPosition: FabPosition = FabPosition.End, + containerColor: Color = MaterialTheme.colorScheme.background, + contentColor: Color = MaterialTheme.colorScheme.onBackground, + contentWindowInsets: WindowInsets = WindowInsets.safeDrawing, + content: @Composable (PaddingValues) -> Unit, +) { + Scaffold( + modifier = modifier, + topBar = topBar, + bottomBar = bottomBar, + snackbarHost = snackbarHost, + floatingActionButton = floatingActionButton, + floatingActionButtonPosition = floatingActionButtonPosition, + containerColor = containerColor, + contentColor = contentColor, + contentWindowInsets = contentWindowInsets, + content = content, + ) +} + +@Composable +fun ExpressiveBadge( + modifier: Modifier = Modifier, + containerColor: Color = MaterialTheme.colorScheme.error, + contentColor: Color = MaterialTheme.colorScheme.onError, + content: @Composable (RowScope.() -> Unit)? = null, +) { + Badge( + modifier = modifier, + containerColor = containerColor, + contentColor = contentColor, + content = content, + ) +} + +@Composable +fun ExpressiveBadgedBox( + badge: @Composable BoxScope.() -> Unit, + modifier: Modifier = Modifier, + content: @Composable BoxScope.() -> Unit, +) { + BadgedBox( + badge = badge, + modifier = modifier, + content = content, + ) +} + +@Composable +fun ExpressiveHorizontalDivider( + modifier: Modifier = Modifier, + color: Color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.6f), + thickness: Dp = 1.dp, +) { + HorizontalDivider( + modifier = modifier, + thickness = thickness, + color = color, + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ExpressivePullToRefreshBox( + isRefreshing: Boolean, + onRefresh: () -> Unit, + modifier: Modifier = Modifier, + content: @Composable BoxScope.() -> Unit, +) { + PullToRefreshBox( + isRefreshing = isRefreshing, + onRefresh = onRefresh, + modifier = modifier, + content = content, + ) +} + +@Composable +fun ExpressiveFilledTonalIconButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + content: @Composable () -> Unit, +) { + val interactionSource = remember { MutableInteractionSource() } + FilledTonalIconButton( + onClick = onClick, + modifier = modifier.expressivePressScale(interactionSource), + enabled = enabled, + interactionSource = interactionSource, + content = content, + ) +} + +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun ExpressiveToggleButton( + checked: Boolean, + onCheckedChange: (Boolean) -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + shapes: ToggleButtonShapes, + content: @Composable RowScope.() -> Unit, +) { + ToggleButton( + checked = checked, + onCheckedChange = onCheckedChange, + modifier = modifier, + enabled = enabled, + shapes = shapes, + content = content, + ) +} + +@Composable +fun ExpressiveLoadingIndicator( + modifier: Modifier = Modifier, + color: Color = MaterialTheme.colorScheme.primary, + size: Dp = 48.dp, +) { + CircularProgressIndicator( + modifier = modifier.size(size), + color = color, + trackColor = color.copy(alpha = 0.18f), + strokeWidth = 5.dp, + gapSize = 3.dp, + ) +} + +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun ExpressiveTopLoadingIndicator( + modifier: Modifier = Modifier, + color: Color = MaterialTheme.colorScheme.primary, + trackColor: Color = color.copy(alpha = 0.18f), +) { + LinearWavyProgressIndicator( + modifier = modifier.fillMaxWidth().height(18.dp), + color = color, + trackColor = trackColor, + stroke = WavyProgressIndicatorDefaults.linearIndicatorStroke, + trackStroke = WavyProgressIndicatorDefaults.linearTrackStroke, + wavelength = WavyProgressIndicatorDefaults.LinearIndeterminateWavelength, + waveSpeed = 12.dp, + ) +} + +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun ExpressiveTopProgressIndicator( + progress: () -> Float, + modifier: Modifier = Modifier, + color: Color = MaterialTheme.colorScheme.primary, + trackColor: Color = color.copy(alpha = 0.18f), +) { + LinearWavyProgressIndicator( + progress = progress, + modifier = modifier.fillMaxWidth().height(18.dp), + color = color, + trackColor = trackColor, + stroke = WavyProgressIndicatorDefaults.linearIndicatorStroke, + trackStroke = WavyProgressIndicatorDefaults.linearTrackStroke, + wavelength = WavyProgressIndicatorDefaults.LinearDeterminateWavelength, + amplitude = WavyProgressIndicatorDefaults.indicatorAmplitude, + stopSize = WavyProgressIndicatorDefaults.LinearTrackStopIndicatorSize, + gapSize = WavyProgressIndicatorDefaults.LinearIndicatorTrackGapSize, + ) +} + +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun ExpressiveLinearProgressIndicator( + progress: () -> Float, + modifier: Modifier = Modifier, + color: Color = MaterialTheme.colorScheme.primary, + trackColor: Color = color.copy(alpha = 0.18f), +) { + LinearWavyProgressIndicator( + progress = progress, + modifier = modifier, + color = color, + trackColor = trackColor, + stroke = WavyProgressIndicatorDefaults.linearIndicatorStroke, + trackStroke = WavyProgressIndicatorDefaults.linearTrackStroke, + wavelength = WavyProgressIndicatorDefaults.LinearDeterminateWavelength, + amplitude = { WavyProgressIndicatorDefaults.indicatorAmplitude(it) * 0.45f }, + stopSize = WavyProgressIndicatorDefaults.LinearTrackStopIndicatorSize, + gapSize = WavyProgressIndicatorDefaults.LinearIndicatorTrackGapSize, + ) +} + +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun ExpressiveLinearProgressIndicator( + modifier: Modifier = Modifier, + color: Color = MaterialTheme.colorScheme.primary, + trackColor: Color = color.copy(alpha = 0.18f), +) { + LinearWavyProgressIndicator( + modifier = modifier, + color = color, + trackColor = trackColor, + stroke = WavyProgressIndicatorDefaults.linearIndicatorStroke, + trackStroke = WavyProgressIndicatorDefaults.linearTrackStroke, + wavelength = WavyProgressIndicatorDefaults.LinearIndeterminateWavelength, + waveSpeed = 8.dp, + ) +} + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun ExpressiveSlider( + value: Float, + onValueChange: (Float) -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + valueRange: ClosedFloatingPointRange = 0f..1f, + steps: Int = 0, +) { + val interactionSource = remember { MutableInteractionSource() } + val colors = SliderDefaults.colors( + thumbColor = MaterialTheme.colorScheme.secondary, + activeTrackColor = MaterialTheme.colorScheme.secondary, + inactiveTrackColor = MaterialTheme.colorScheme.secondary.copy(alpha = 0.18f), + activeTickColor = MaterialTheme.colorScheme.onSecondary, + inactiveTickColor = MaterialTheme.colorScheme.secondary.copy(alpha = 0.38f), + ) + Slider( + value = value, + onValueChange = onValueChange, + modifier = modifier, + enabled = enabled, + valueRange = valueRange, + steps = steps, + colors = colors, + interactionSource = interactionSource, + thumb = { sliderState -> + SliderDefaults.Thumb( + interactionSource = interactionSource, + sliderState = sliderState, + colors = colors, + enabled = enabled, + thumbSize = DpSize(6.dp, 34.dp), + ) + }, + track = { sliderState -> + val stopColor = MaterialTheme.colorScheme.secondary + SliderDefaults.Track( + sliderState = sliderState, + colors = colors, + enabled = enabled, + trackInsideCornerSize = 8.dp, + thumbTrackGapSize = 6.dp, + drawStopIndicator = { offset -> + drawCircle( + color = stopColor, + radius = 4.dp.toPx(), + center = offset, + ) + }, + ) + }, + ) +} diff --git a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/ExpressiveSurfaces.kt b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/ExpressiveSurfaces.kt new file mode 100644 index 0000000..6978b5f --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/ExpressiveSurfaces.kt @@ -0,0 +1,95 @@ +package com.derakkuma.ui.components + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +@Composable +fun Modifier.expressiveScreenBackground(): Modifier = + background(MaterialTheme.colorScheme.background) + +@Composable +fun ExpressiveGlassCard( + modifier: Modifier = Modifier, + cornerRadius: Dp = 28.dp, + containerColor: Color = Color.White, + content: @Composable BoxScope.() -> Unit, +) { + val shape = RoundedCornerShape(cornerRadius) + Surface( + modifier = modifier, + shape = shape, + color = containerColor, + tonalElevation = 0.dp, + shadowElevation = 0.dp, + ) { + Box( + modifier = Modifier + .clip(shape) + .background(containerColor) + .border(BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.55f)), shape), + content = content, + ) + } +} + +@Composable +fun ExpressiveIconSurface( + modifier: Modifier = Modifier, + size: Dp = 40.dp, + shape: Shape = MaterialTheme.shapes.extraLarge, + containerColor: Color = MaterialTheme.colorScheme.primaryContainer, + content: @Composable BoxScope.() -> Unit, +) { + Box( + modifier = modifier + .size(size) + .clip(shape) + .background(containerColor), + content = content, + ) +} + +@Composable +fun ExpressiveBadgeSurface( + modifier: Modifier = Modifier, + shape: Shape = MaterialTheme.shapes.extraLarge, + containerColor: Color, + content: @Composable BoxScope.() -> Unit, +) { + Box( + modifier = modifier + .clip(shape) + .background(containerColor), + content = content, + ) +} + +@Composable +fun ExpressiveDot( + modifier: Modifier = Modifier, + size: Dp = 10.dp, + color: Color, +) { + Box( + modifier = modifier + .size(size) + .clip(MaterialTheme.shapes.extraLarge) + .background(color) + .padding(0.dp), + ) +} diff --git a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/LoadingContent.kt b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/LoadingContent.kt index 8fcad20..500f64b 100644 --- a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/LoadingContent.kt +++ b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/LoadingContent.kt @@ -40,15 +40,15 @@ fun LoadingContent( @Composable fun BigStat(value: String, label: String) { Column(horizontalAlignment = Alignment.CenterHorizontally) { - Text(value, fontSize = 28.sp, fontWeight = FontWeight.Bold, color = DarkText) - Text(label, fontSize = 12.sp, color = MutedText) + Text(value, fontSize = 28.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) + Text(label, fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } } @Composable fun LoadingState(modifier: Modifier = Modifier.fillMaxWidth().padding(top = 60.dp), color: Color = MaterialTheme.colorScheme.onBackground) { Box(modifier, contentAlignment = Alignment.Center) { - CircularProgressIndicator(color = color) + ExpressiveLoadingIndicator(color = color) } } @@ -61,8 +61,14 @@ fun ErrorState(error: String, onRetry: (() -> Unit)? = null, modifier: Modifier ) { Text(error, fontSize = 14.sp, color = MaterialTheme.colorScheme.error, fontWeight = FontWeight.Medium) if (onRetry != null) { - TextButton(onClick = onRetry) { - Text("Retry", color = PrimaryBlue, fontWeight = FontWeight.SemiBold) + ExpressiveButton( + onClick = onRetry, + colors = ButtonDefaults.filledTonalButtonColors( + containerColor = MaterialTheme.colorScheme.errorContainer, + contentColor = MaterialTheme.colorScheme.onErrorContainer, + ), + ) { + Text("Retry", fontWeight = FontWeight.Bold) } } } @@ -71,7 +77,7 @@ fun ErrorState(error: String, onRetry: (() -> Unit)? = null, modifier: Modifier @Composable fun EmptyState(message: String, modifier: Modifier = Modifier.fillMaxWidth().padding(24.dp)) { Box(modifier, contentAlignment = Alignment.Center) { - Text(message, color = MutedText, fontWeight = FontWeight.Medium) + Text(message, color = MaterialTheme.colorScheme.onSurfaceVariant, fontWeight = FontWeight.Medium) } } @@ -84,25 +90,30 @@ fun InfoCard(text: String, color: Color, modifier: Modifier = Modifier.fillMaxWi @Composable fun EmptyCard(text: String, modifier: Modifier = Modifier.fillMaxWidth()) { - InfoCard(text, MutedText, modifier) + InfoCard(text, MaterialTheme.colorScheme.onSurfaceVariant, modifier) } @Composable fun SectionHeader(text: String, modifier: Modifier = Modifier.padding(top = 8.dp, start = 4.dp)) { - Text(text, fontSize = 14.sp, fontWeight = FontWeight.Bold, color = DarkText, modifier = modifier) + Text( + text, + style = MaterialTheme.typography.titleSmall, + color = MaterialTheme.colorScheme.onSurface, + modifier = modifier, + ) } @Composable fun DerakkumaCard( modifier: Modifier = Modifier.fillMaxWidth(), - containerColor: Color = CardWhite, - cornerRadius: Dp = 8.dp, + containerColor: Color = MaterialTheme.colorScheme.surfaceContainerHigh, + cornerRadius: Dp = 24.dp, content: @Composable () -> Unit, ) { - Card( + ExpressiveGlassCard( modifier = modifier, - shape = androidx.compose.foundation.shape.RoundedCornerShape(cornerRadius), - colors = CardDefaults.cardColors(containerColor = containerColor), + cornerRadius = cornerRadius, + containerColor = containerColor, ) { content() } diff --git a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/MaimaiProfile.kt b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/MaimaiProfile.kt index 6446997..a1f76d3 100644 --- a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/MaimaiProfile.kt +++ b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/MaimaiProfile.kt @@ -19,8 +19,7 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -43,10 +42,6 @@ import com.derakkuma.ui.images.MaimaiRemoteImage import com.derakkuma.ui.images.ratingPlateImage import com.derakkuma.ui.images.rememberBundledImage import com.derakkuma.ui.images.trophyPlateImage -import com.derakkuma.ui.theme.CardWhite -import com.derakkuma.ui.theme.DarkText -import com.derakkuma.ui.theme.MutedText -import com.derakkuma.ui.theme.PrimaryBlue @Composable fun MaimaiProfile( @@ -72,12 +67,11 @@ fun MaimaiProfile( details: @Composable ColumnScope.() -> Unit = {}, actions: @Composable RowScope.() -> Unit = {}, ) { - Card( + ExpressiveGlassCard( modifier = modifier .fillMaxWidth() .then(if (onClick != null) Modifier.clickable { onClick() } else Modifier), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), + cornerRadius = 24.dp, ) { Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { Row(verticalAlignment = Alignment.Top, horizontalArrangement = Arrangement.spacedBy(10.dp)) { @@ -105,7 +99,7 @@ fun MaimaiProfile( "×$stars", fontSize = ratingTextSize, fontWeight = if (ratingPlate != null) FontWeight.SemiBold else FontWeight.Normal, - color = if (ratingPlate != null) DarkText else MutedText, + color = if (ratingPlate != null) MaterialTheme.colorScheme.onSurface else MaterialTheme.colorScheme.onSurfaceVariant, ) } ratingTrailing() @@ -118,7 +112,7 @@ fun MaimaiProfile( } if (showComment && comment.isNotBlank()) { - Text(comment, fontSize = 12.sp, color = MutedText) + Text(comment, fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { @@ -135,7 +129,7 @@ private fun MaimaiRating( fontSize: TextUnit, ) { if (ratingPlate == null) { - Text("Rating $rating", fontSize = fontSize, fontWeight = FontWeight.SemiBold, color = PrimaryBlue) + Text("Rating $rating", fontSize = fontSize, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.primary) return } @@ -150,10 +144,10 @@ private fun MaimaiRating( ) } Text( - "$rating ", + "$rating ", fontSize = fontSize, fontWeight = FontWeight.ExtraBold, - color = Color.White, + color = MaterialTheme.colorScheme.onPrimary, ) } } @@ -177,7 +171,7 @@ fun MaimaiProfileHeader( ) { MaimaiRemoteImage( url = iconUrl, - modifier = Modifier.size(iconSize).clip(RoundedCornerShape(6.dp)), + modifier = Modifier.size(iconSize).clip(RoundedCornerShape(2.dp)), contentDescription = "Profile icon", contentScale = ContentScale.Crop, ) @@ -188,7 +182,7 @@ fun MaimaiProfileHeader( modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(3.dp), ) { - Text(name, fontSize = nameFontSize, fontWeight = FontWeight.Bold, color = DarkText, maxLines = 1) + Text(name, fontSize = nameFontSize, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface, maxLines = 1) if (title.isNotBlank()) { MaimaiTrophyTitle(title = title, rarity = titleRarity) } @@ -249,9 +243,9 @@ fun MaimaiTrophyTitle( textAlign = TextAlign.Center, style = TextStyle( fontSize = 12.sp, - color = Color.White, + color = MaterialTheme.colorScheme.onPrimary, shadow = Shadow( - color = Color.Black, + color = MaterialTheme.colorScheme.scrim, blurRadius = 4f, ), ), diff --git a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/MaintenanceBanner.kt b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/MaintenanceBanner.kt index 73882ef..634f237 100644 --- a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/MaintenanceBanner.kt +++ b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/MaintenanceBanner.kt @@ -1,5 +1,6 @@ package com.derakkuma.ui.components +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -10,7 +11,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.sp import com.derakkuma.data.MaintenanceMode -import com.derakkuma.ui.theme.CardWhite import kotlinx.coroutines.delay @Composable @@ -38,7 +38,7 @@ fun MaintenanceBanner() { countdown, fontSize = 20.sp, fontWeight = FontWeight.ExtraBold, - color = CardWhite, + color = MaterialTheme.colorScheme.onError, ) }, ) diff --git a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/MessageBanner.kt b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/MessageBanner.kt index 6f7fc95..9245f97 100644 --- a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/MessageBanner.kt +++ b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/MessageBanner.kt @@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -26,21 +27,20 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.derakkuma.ui.images.MaimaiAssets -import com.derakkuma.ui.theme.CardWhite -import com.derakkuma.ui.theme.DangerRed @Composable fun MessageBanner( title: String, message: String, contentDescription: String, - backgroundColor: Color = DangerRed.copy(alpha = 0.9f), + backgroundColor: Color = MaterialTheme.colorScheme.error.copy(alpha = 0.9f), modifier: Modifier = Modifier, messageAlpha: Float = 0.75f, fallbackIcon: @Composable (() -> Unit)? = null, trailingContent: @Composable (() -> Unit)? = null, ) { var charaBitmap by remember { mutableStateOf(null) } + val contentColor = MaterialTheme.colorScheme.onError LaunchedEffect(Unit) { charaBitmap = MaimaiAssets.load("chara_01.png") } @@ -73,12 +73,12 @@ fun MessageBanner( title, fontSize = 14.sp, fontWeight = FontWeight.Bold, - color = CardWhite, + color = contentColor, ) Text( message, fontSize = 11.sp, - color = CardWhite.copy(alpha = messageAlpha), + color = contentColor.copy(alpha = messageAlpha), ) } diff --git a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/PlayHeatmap.kt b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/PlayHeatmap.kt index f0e9657..dab2499 100644 --- a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/PlayHeatmap.kt +++ b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/PlayHeatmap.kt @@ -5,9 +5,7 @@ import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults -import androidx.compose.material3.Snackbar +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment @@ -70,14 +68,15 @@ fun PlayHeatmap( var toastText by remember { mutableStateOf(null) } val scope = rememberCoroutineScope() + val inactiveHeatColor = MaterialTheme.colorScheme.surfaceVariant + val activeHeatColor = MaterialTheme.colorScheme.secondary - Card( + ExpressiveGlassCard( modifier = modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), + cornerRadius = 24.dp, ) { Column(modifier = Modifier.padding(12.dp)) { - Text("Play Activity", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = DarkText) + Text("Play Activity", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.onSurface) Spacer(Modifier.height(6.dp)) HeatMap( @@ -86,9 +85,9 @@ fun PlayHeatmap( heatStyle = HeatStyle( height = 14.dp, heatColor = HeatColor( - inactiveColor = Color(0xFFE5E7EB), - activeLowestColor = Color(0xFFE5E7EB), // same as inactive so 0-play days look gray - activeHighestColor = Color(0xFFFF699D), + inactiveColor = inactiveHeatColor, + activeLowestColor = inactiveHeatColor, // same as inactive so 0-play days look gray + activeHighestColor = activeHeatColor, ), heatShape = RoundedCornerShape(2.dp), showHeatValue = false, @@ -118,9 +117,8 @@ fun PlayHeatmap( ) { Column { Spacer(Modifier.height(6.dp)) - Snackbar( + ExpressiveSnackbar( modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(6.dp), ) { Text(toastText ?: "", fontSize = 12.sp) } diff --git a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/SongRow.kt b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/SongRow.kt index b82404d..763e0ea 100644 --- a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/SongRow.kt +++ b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/SongRow.kt @@ -2,12 +2,16 @@ package com.derakkuma.ui.components import androidx.compose.foundation.background import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.MaterialShapes +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text +import androidx.compose.material3.toShape import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow @@ -24,6 +28,7 @@ private const val IMG_BASE = "https://maimaidx-eng.com/maimai-mobile/img" * Reusable song row with album art, difficulty badge, name, badges, and score. * Used in both Record (playlog) and Rating tabs. */ +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun SongRow( name: String, @@ -43,46 +48,52 @@ fun SongRow( datetime: String = "", // Whether to use playlog-style rank images or music_icon style usePlaylogRanks: Boolean = true, + containerColor: Color = Color.Transparent, ) { Row( - modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp), + modifier = Modifier + .fillMaxWidth() + .clip(MaterialTheme.shapes.extraLarge) + .background(containerColor) + .padding(horizontal = 12.dp, vertical = 8.dp), verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp), ) { - // Album art - Box(modifier = Modifier.size(artSize)) { - if (albumArtUrl != null) { + // Album art + Box(modifier = Modifier.size(artSize)) { + if (albumArtUrl != null) { + MaimaiRemoteImage( + url = albumArtUrl, + modifier = Modifier.fillMaxSize().clip(MaterialShapes.Square.toShape()), + contentDescription = name, + contentScale = ContentScale.Crop, + ) + } else { + Box( + modifier = Modifier.fillMaxSize() + .clip(MaterialShapes.Square.toShape()) + .background(MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.1f)), + ) + } + // Difficulty badge overlaid bottom-left MaimaiRemoteImage( - url = albumArtUrl, - modifier = Modifier.fillMaxSize().clip(RoundedCornerShape(4.dp)), - contentDescription = name, - contentScale = ContentScale.Crop, - ) - } else { - Box( - modifier = Modifier.fillMaxSize() - .clip(RoundedCornerShape(4.dp)) - .background(MutedText.copy(alpha = 0.1f)), + url = "$IMG_BASE/diff_$difficulty.png", + modifier = Modifier.height(14.dp).align(Alignment.BottomStart), + contentDescription = difficulty, ) } - // Difficulty badge overlaid bottom-left - MaimaiRemoteImage( - url = "$IMG_BASE/diff_$difficulty.png", - modifier = Modifier.height(14.dp).align(Alignment.BottomStart), - contentDescription = difficulty, - ) - } - Spacer(Modifier.width(8.dp)) - - // Song info - Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(2.dp)) { + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { // Title row Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) { Text( name, fontSize = 13.sp, fontWeight = FontWeight.SemiBold, - color = DarkText, + color = MaterialTheme.colorScheme.onSurface, maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier.weight(1f, fill = false), @@ -128,32 +139,32 @@ fun SongRow( if (placingImg != null) { BundledIcon(image = placingImg, modifier = Modifier.height(14.dp), contentDescription = placing) } else { - Text(placing, fontSize = 10.sp, color = MutedText) + Text(placing, fontSize = 10.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } } } } - // Score + rank column - Column(horizontalAlignment = Alignment.End) { - Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(3.dp)) { - Text( - "${formatScore(score)}%", - fontSize = 13.sp, - fontWeight = FontWeight.Bold, - color = AccentPink, - ) - val rankIcon = if (usePlaylogRanks) scoreRankImage(scoreRank) else musicIconImage(scoreRank) - if (rankIcon != null) { - BundledIcon(image = rankIcon, modifier = Modifier.height(16.dp), contentDescription = scoreRank) + // Score + rank column + Column(horizontalAlignment = Alignment.End) { + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(3.dp)) { + Text( + "${formatScore(score)}%", + fontSize = 13.sp, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.secondary, + ) + val rankIcon = if (usePlaylogRanks) scoreRankImage(scoreRank) else musicIconImage(scoreRank) + if (rankIcon != null) { + BundledIcon(image = rankIcon, modifier = Modifier.height(16.dp), contentDescription = scoreRank) + } + } + if (dxScore.isNotBlank()) { + Text("DX $dxScore", fontSize = 10.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) + } + if (datetime.isNotBlank()) { + Text(datetime, fontSize = 10.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } } - if (dxScore.isNotBlank()) { - Text("DX $dxScore", fontSize = 10.sp, color = MutedText) - } - if (datetime.isNotBlank()) { - Text(datetime, fontSize = 10.sp, color = MutedText) - } - } } } diff --git a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/SplitButtonRow.kt b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/SplitButtonRow.kt index 073ff63..fccfbb7 100644 --- a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/SplitButtonRow.kt +++ b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/SplitButtonRow.kt @@ -1,33 +1,23 @@ package com.derakkuma.ui.components -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.defaultMinSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ButtonGroupDefaults +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.derakkuma.ui.theme.CardWhite -import com.derakkuma.ui.theme.DarkText -import com.derakkuma.ui.theme.MutedText -import com.derakkuma.ui.theme.PrimaryBlue +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun SplitButtonRow( label: String, @@ -38,46 +28,40 @@ fun SplitButtonRow( description: String? = null, disabled: Boolean = false, icons: Map? = null, - minOptionWidth: Dp = 44.dp, - minOptionHeight: Dp = 38.dp, - unselectedContainerColor: Color = MutedText.copy(alpha = 0.1f), ) { Row( - modifier = modifier.fillMaxWidth().padding(vertical = 4.dp), + modifier = modifier.padding(vertical = 4.dp), verticalAlignment = Alignment.CenterVertically, ) { Column(modifier = Modifier.weight(1f)) { - Text(label, fontSize = 13.sp, fontWeight = FontWeight.Medium, color = DarkText) + Text(label, fontSize = 13.sp, fontWeight = FontWeight.Medium, color = MaterialTheme.colorScheme.onSurface) if (!description.isNullOrBlank()) { - Text(description, fontSize = 11.sp, color = MutedText) + Text(description, fontSize = 11.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } } - Row(modifier = Modifier.clip(RoundedCornerShape(6.dp))) { + Row( + horizontalArrangement = androidx.compose.foundation.layout.Arrangement.spacedBy( + ButtonGroupDefaults.ConnectedSpaceBetween, + ), + ) { options.forEachIndexed { i, (value, optLabel) -> val selectedOption = value == selected - val bgColor = if (selectedOption) PrimaryBlue else unselectedContainerColor - val textColor = if (selectedOption) CardWhite else DarkText val icon = icons?.get(i) - - Box( - modifier = Modifier - .defaultMinSize(minWidth = minOptionWidth, minHeight = minOptionHeight) - .background(if (disabled) bgColor.copy(alpha = 0.4f) else bgColor) - .clickable(enabled = !disabled) { onSelect(value) } - .padding(horizontal = 12.dp, vertical = 6.dp), - contentAlignment = Alignment.Center, + ExpressiveToggleButton( + checked = selectedOption, + onCheckedChange = { onSelect(value) }, + enabled = !disabled, + shapes = when (i) { + 0 -> ButtonGroupDefaults.connectedLeadingButtonShapes() + options.lastIndex -> ButtonGroupDefaults.connectedTrailingButtonShapes() + else -> ButtonGroupDefaults.connectedMiddleButtonShapes() + }, ) { if (icon != null) { - Icon(icon, contentDescription = optLabel, modifier = Modifier.size(18.dp), tint = textColor) + Icon(icon, contentDescription = optLabel, modifier = Modifier.size(18.dp)) } else { - Text( - optLabel, - fontSize = 12.sp, - fontWeight = if (selectedOption) FontWeight.Bold else FontWeight.Medium, - color = textColor, - textAlign = TextAlign.Center, - ) + Text(optLabel) } } } diff --git a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/VerizonNetworkBanner.kt b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/VerizonNetworkBanner.kt index 056053b..1ffa022 100644 --- a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/VerizonNetworkBanner.kt +++ b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/components/VerizonNetworkBanner.kt @@ -4,10 +4,10 @@ import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.WifiOff import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import com.derakkuma.ui.theme.CardWhite @Composable fun VerizonNetworkBanner() { @@ -21,7 +21,7 @@ fun VerizonNetworkBanner() { imageVector = Icons.Outlined.WifiOff, contentDescription = "Verizon network warning", modifier = Modifier.size(40.dp), - tint = CardWhite, + tint = MaterialTheme.colorScheme.onError, ) }, ) diff --git a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/AppIconTab.kt b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/AppIconTab.kt index df492fd..6a874a3 100644 --- a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/AppIconTab.kt +++ b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/AppIconTab.kt @@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment @@ -31,6 +30,11 @@ import com.derakkuma.appicon.setAppIcon import com.derakkuma.atproto.openOAuthUrl import com.derakkuma.data.CacheStore import com.derakkuma.donation.DonationUnlock +import com.derakkuma.ui.components.ExpressiveGlassCard +import com.derakkuma.ui.components.ExpressiveTextButton +import com.derakkuma.ui.components.ExpressiveAlertDialog +import com.derakkuma.ui.components.ExpressiveSnackbar +import com.derakkuma.ui.components.ExpressiveScaffold import com.derakkuma.ui.components.SplitButtonRow import com.derakkuma.ui.theme.* import io.github.vinceglb.confettikit.compose.ConfettiKit @@ -64,7 +68,7 @@ fun AppIconTab(cacheStore: CacheStore) { } } - Scaffold( + ExpressiveScaffold( contentWindowInsets = WindowInsets(0.dp), ) { padding -> Column( @@ -72,7 +76,7 @@ fun AppIconTab(cacheStore: CacheStore) { verticalArrangement = Arrangement.spacedBy(12.dp), ) { Column(Modifier.padding(horizontal = 12.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) { - Text("App Icon", fontSize = 20.sp, fontWeight = FontWeight.Bold, color = DarkText) + Text("App Icon", fontSize = 20.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) } SplitButtonRow( @@ -84,8 +88,6 @@ fun AppIconTab(cacheStore: CacheStore) { selected = previewMode, onSelect = { previewMode = it }, modifier = Modifier.padding(horizontal = 12.dp), - minOptionWidth = 96.dp, - unselectedContainerColor = CardWhite, ) LazyVerticalGrid( @@ -114,7 +116,7 @@ fun AppIconTab(cacheStore: CacheStore) { } pendingPartnerIcon?.let { option -> - AlertDialog( + ExpressiveAlertDialog( onDismissRequest = { pendingPartnerIcon = null }, title = { Text("Support Derakkuma") }, text = { @@ -124,7 +126,7 @@ fun AppIconTab(cacheStore: CacheStore) { } }, confirmButton = { - TextButton( + ExpressiveTextButton( onClick = { scope.launch { val nonce = DonationUnlock.friendCodeNonce(cacheStore) @@ -137,10 +139,10 @@ fun AppIconTab(cacheStore: CacheStore) { }, dismissButton = { Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) { - TextButton(onClick = { pendingPartnerIcon = null }) { + ExpressiveTextButton(onClick = { pendingPartnerIcon = null }) { Text("Not now") } - TextButton( + ExpressiveTextButton( onClick = { scope.launch { checkingRestore = true @@ -167,10 +169,9 @@ fun AppIconTab(cacheStore: CacheStore) { if (snackMsg != null) { Popup(alignment = Alignment.BottomCenter) { - Snackbar( + ExpressiveSnackbar( modifier = Modifier.padding(12.dp), - shape = RoundedCornerShape(8.dp), - action = { TextButton(onClick = { snackMsg = null }) { Text("OK") } }, + action = { ExpressiveTextButton(onClick = { snackMsg = null }) { Text("OK") } }, ) { Text(snackMsg!!) } } } @@ -200,10 +201,10 @@ private fun donationConfetti(): List = listOf( @Composable private fun AppIconCard(option: AppIconOption, selected: Boolean, alternatePreview: Boolean, onClick: () -> Unit) { - Card( + ExpressiveGlassCard( modifier = Modifier.fillMaxWidth().clickable(onClick = onClick), - shape = RoundedCornerShape(26.dp), - colors = CardDefaults.cardColors(containerColor = if (selected) Color(0xFFFFCDDD) else CardWhite), + cornerRadius = 26.dp, + containerColor = if (selected) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surfaceContainerHigh, ) { Column( modifier = Modifier.fillMaxWidth().padding(horizontal = 6.dp, vertical = 8.dp), @@ -211,7 +212,13 @@ private fun AppIconCard(option: AppIconOption, selected: Boolean, alternatePrevi verticalArrangement = Arrangement.spacedBy(6.dp), ) { AppIconPreview(option = option, alternatePreview = alternatePreview) - Text(option.label, fontSize = 11.sp, fontWeight = FontWeight.SemiBold, color = DarkText, maxLines = 1) + Text( + option.label, + fontSize = 11.sp, + fontWeight = FontWeight.SemiBold, + color = if (selected) MaterialTheme.colorScheme.onPrimaryContainer else MaterialTheme.colorScheme.onSurface, + maxLines = 1, + ) } } } @@ -235,9 +242,10 @@ private fun AppIconPreview(option: AppIconOption, alternatePreview: Boolean) { value = if (usesIosPreview) null else loadAppIconPreviewAsset(fgName) } val (androidMonochromeBackground, androidMonochromeForeground) = platformMonochromeAppIconColors() + val defaultPreviewBackground = MaterialTheme.colorScheme.primaryContainer - val previewModifier = Modifier.fillMaxWidth().aspectRatio(1f).clip(RoundedCornerShape(18.dp)).let { modifier -> - if (usesIosPreview) modifier else modifier.background(if (alternatePreview) androidMonochromeBackground else Color(0xFFEAF7FF)) + val previewModifier = Modifier.fillMaxWidth().aspectRatio(1f).clip(MaterialTheme.shapes.extraLarge).let { modifier -> + if (usesIosPreview) modifier else modifier.background(if (alternatePreview) androidMonochromeBackground else defaultPreviewBackground) } Box( diff --git a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/AppSettingsTab.kt b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/AppSettingsTab.kt index 6f2a396..9f3db63 100644 --- a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/AppSettingsTab.kt +++ b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/AppSettingsTab.kt @@ -2,7 +2,6 @@ package com.derakkuma.ui.tabs import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.Logout @@ -15,6 +14,14 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.derakkuma.data.CacheStore +import com.derakkuma.ui.components.ExpressiveButton +import com.derakkuma.ui.components.ExpressiveGlassCard +import com.derakkuma.ui.components.ExpressiveSwitch +import com.derakkuma.ui.components.ExpressiveOutlinedButton +import com.derakkuma.ui.components.ExpressiveTextButton +import com.derakkuma.ui.components.ExpressiveAlertDialog +import com.derakkuma.ui.components.ExpressiveSnackbar +import com.derakkuma.ui.components.ExpressiveScaffold import com.derakkuma.ui.components.SplitButtonRow import com.derakkuma.ui.theme.* import io.github.samuolis.posthog.PostHog @@ -31,14 +38,13 @@ fun AppSettingsTab( var snackMsg by remember { mutableStateOf(null) } var showLogoutConfirm by remember { mutableStateOf(false) } - Scaffold( + ExpressiveScaffold( contentWindowInsets = WindowInsets(0.dp), snackbarHost = { if (snackMsg != null) { - Snackbar( + ExpressiveSnackbar( modifier = Modifier.padding(12.dp), - shape = RoundedCornerShape(8.dp), - action = { TextButton(onClick = { snackMsg = null }) { Text("OK") } }, + action = { ExpressiveTextButton(onClick = { snackMsg = null }) { Text("OK") } }, ) { Text(snackMsg!!) } } }, @@ -47,21 +53,17 @@ fun AppSettingsTab( modifier = Modifier.fillMaxSize().padding(padding).verticalScroll(rememberScrollState()).padding(start = 12.dp, end = 12.dp, bottom = 88.dp), verticalArrangement = Arrangement.spacedBy(8.dp), ) { - Text("App Settings", fontSize = 20.sp, fontWeight = FontWeight.Bold, color = DarkText, modifier = Modifier.padding(bottom = 4.dp)) + Text("App Settings", fontSize = 20.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface, modifier = Modifier.padding(bottom = 4.dp)) // Account section - Card( + ExpressiveGlassCard( modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), ) { Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { - Text("Account", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = DarkText) - Text("Sign out of your SEGA account. You'll need to log in again to access your data.", fontSize = 12.sp, color = MutedText) - OutlinedButton( + Text("Account", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) + Text("Sign out of your SEGA account. You'll need to log in again to access your data.", fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) + ExpressiveOutlinedButton( onClick = { showLogoutConfirm = true }, modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = ButtonDefaults.outlinedButtonColors(contentColor = DangerRed), ) { Icon(Icons.AutoMirrored.Filled.Logout, contentDescription = null, modifier = Modifier.size(18.dp)) Spacer(Modifier.width(8.dp)) @@ -71,15 +73,13 @@ fun AppSettingsTab( } // Appearance section - Card( + ExpressiveGlassCard( modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), ) { Column(modifier = Modifier.fillMaxWidth().padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { Column { - Text("Appearance", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = DarkText) - Text("Customize Derakkuma's look and feel", fontSize = 12.sp, color = MutedText) + Text("Appearance", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) + Text("Customize Derakkuma's look and feel", fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } var selectedIconStyle by remember(iconStyle) { mutableStateOf(iconStyle.takeIf { it == "flat" } ?: "fun") } @@ -101,17 +101,16 @@ fun AppSettingsTab( verticalAlignment = Alignment.CenterVertically, ) { Column(modifier = Modifier.weight(1f)) { - Text("Park animation", fontSize = 13.sp, fontWeight = FontWeight.Medium, color = DarkText) - Text("Scrolling park animation above the navbar", fontSize = 11.sp, color = MutedText) + Text("Park animation", fontSize = 13.sp, fontWeight = FontWeight.Medium, color = MaterialTheme.colorScheme.onSurface) + Text("Scrolling park animation above the navbar", fontSize = 11.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } var parkState by remember { mutableStateOf(cacheStore.getSetting("parkAnimation") != "false") } - Switch( + ExpressiveSwitch( checked = parkState, onCheckedChange = { parkState = it cacheStore.putSetting("parkAnimation", if (it) "true" else "false") - }, - colors = SwitchDefaults.colors(checkedTrackColor = PrimaryBlue), + } ) } } @@ -119,10 +118,8 @@ fun AppSettingsTab( // Analytics section if (Secrets.POSTHOG_API_KEY.isNotEmpty()) { - Card( + ExpressiveGlassCard( modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), ) { Row( modifier = Modifier.fillMaxWidth().padding(16.dp), @@ -130,41 +127,37 @@ fun AppSettingsTab( verticalAlignment = Alignment.CenterVertically, ) { Column(modifier = Modifier.weight(1f)) { - Text("Analytics", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = DarkText) - Text("Fully anonymous and private", fontSize = 12.sp, color = MutedText) + Text("Analytics", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) + Text("Fully anonymous and private", fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } var analyticsEnabled by remember { mutableStateOf(cacheStore.getSetting("analyticsOptOut") != "true") } - Switch( + ExpressiveSwitch( checked = analyticsEnabled, onCheckedChange = { analyticsEnabled = it cacheStore.putSetting("analyticsOptOut", if (it) "false" else "true") if (it) PostHog.optIn() else PostHog.optOut() - }, - colors = SwitchDefaults.colors(checkedTrackColor = PrimaryBlue), + } ) } } } // Cache section - Card( + ExpressiveGlassCard( modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), ) { Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { - Text("Cache", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = DarkText) - Text("Clear cached profile, play log, rating data, and app preferences. Useful if data appears wrong or corrupted.", fontSize = 12.sp, color = MutedText) - Button( + Text("Cache", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) + Text("Clear cached profile, play log, rating data, and app preferences. Useful if data appears wrong or corrupted.", fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) + ExpressiveButton( onClick = { cacheStore.clear() snackMsg = "Cache cleared" onCacheCleared() }, modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = ButtonDefaults.buttonColors(containerColor = DangerRed), + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.error), ) { Icon(Icons.Default.DeleteSweep, contentDescription = null, modifier = Modifier.size(18.dp)) Spacer(Modifier.width(8.dp)) @@ -174,13 +167,11 @@ fun AppSettingsTab( } // About section - Card( + ExpressiveGlassCard( modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), ) { Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { - Text("About", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = DarkText) + Text("About", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) // Row( // modifier = Modifier.fillMaxWidth(), // horizontalArrangement = Arrangement.SpaceBetween, @@ -195,7 +186,7 @@ fun AppSettingsTab( // Text("Built with", fontSize = 13.sp, color = MutedText) // Text("Compose Multiplatform", fontSize = 13.sp, fontWeight = FontWeight.SemiBold, color = DarkText) // } - Text("Derakkuma is an unofficial client for maimai DX NET. Not affiliated with SEGA.", fontSize = 11.sp, color = MutedText) + Text("Derakkuma is an unofficial client for maimai DX NET. Not affiliated with SEGA.", fontSize = 11.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } } } @@ -203,23 +194,21 @@ fun AppSettingsTab( // Logout confirmation dialog if (showLogoutConfirm) { - AlertDialog( + ExpressiveAlertDialog( onDismissRequest = { showLogoutConfirm = false }, title = { Text("Log Out?") }, text = { Text("You'll need to log in again to access your data.") }, confirmButton = { - TextButton( + ExpressiveTextButton( onClick = { showLogoutConfirm = false onLogout() }, - colors = ButtonDefaults.textButtonColors(contentColor = DangerRed), - ) { Text("Log Out", fontWeight = FontWeight.Bold) } + ) { Text("Log Out", fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.error) } }, dismissButton = { - TextButton(onClick = { showLogoutConfirm = false }) { Text("Cancel") } + ExpressiveTextButton(onClick = { showLogoutConfirm = false }) { Text("Cancel") } }, - containerColor = CardWhite, ) } } diff --git a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/AtmosphereTab.kt b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/AtmosphereTab.kt index f47b7e4..ee223c6 100644 --- a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/AtmosphereTab.kt +++ b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/AtmosphereTab.kt @@ -5,8 +5,8 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text -import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight @@ -17,7 +17,7 @@ import com.derakkuma.atproto.AtProtoSection import com.derakkuma.data.CacheStore import com.derakkuma.data.MaimaiClient import com.derakkuma.data.PlayStore -import com.derakkuma.ui.theme.DarkText +import com.derakkuma.ui.components.ExpressivePullToRefreshBox @Composable fun AtmosphereTab( @@ -31,7 +31,7 @@ fun AtmosphereTab( var refreshKey by remember { mutableStateOf(0) } var refreshing by remember { mutableStateOf(false) } - PullToRefreshBox( + ExpressivePullToRefreshBox( isRefreshing = refreshing, onRefresh = { refreshing = true @@ -43,7 +43,7 @@ fun AtmosphereTab( .verticalScroll(rememberScrollState()) .padding(start = 12.dp, end = 12.dp, top = 12.dp, bottom = 88.dp), ) { - Text("Atmosphere", fontSize = 20.sp, fontWeight = FontWeight.Bold, color = DarkText, modifier = Modifier.padding(bottom = 8.dp)) + Text("Atmosphere", fontSize = 20.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface, modifier = Modifier.padding(bottom = 8.dp)) AtProtoSection( client = atProtoClient, cacheStore = cacheStore, diff --git a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/BubbleTab.kt b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/BubbleTab.kt index 64d429f..2ab942b 100644 --- a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/BubbleTab.kt +++ b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/BubbleTab.kt @@ -4,9 +4,8 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.automirrored.outlined.BluetoothSearching import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.BluetoothSearching import androidx.compose.material.icons.outlined.PersonAdd import androidx.compose.material3.* import androidx.compose.runtime.* @@ -21,6 +20,10 @@ import com.derakkuma.bubble.BubbleProfile import com.derakkuma.data.CacheStore import com.derakkuma.data.MaimaiClient import com.derakkuma.data.Profile +import com.derakkuma.ui.components.ExpressiveButton +import com.derakkuma.ui.components.ExpressiveGlassCard +import com.derakkuma.ui.components.ExpressiveIconSurface +import com.derakkuma.ui.components.ExpressiveLoadingIndicator import com.derakkuma.ui.images.MaimaiRemoteImage import com.derakkuma.ui.theme.* import kotlinx.coroutines.launch @@ -42,22 +45,22 @@ fun BubbleTab(client: MaimaiClient, cookie: String, cacheStore: CacheStore) { DisposableEffect(Unit) { onDispose { manager.stop() } } Column(Modifier.fillMaxSize().padding(12.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { - Text("Bubble", fontSize = 20.sp, fontWeight = FontWeight.Bold, color = DarkText) - Card(shape = RoundedCornerShape(14.dp), colors = CardDefaults.cardColors(containerColor = CardWhite)) { + Text("Bubble", fontSize = 20.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) + ExpressiveGlassCard(cornerRadius = 28.dp) { Column(Modifier.fillMaxWidth().padding(14.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(10.dp)) { - Surface(shape = CircleShape, color = PrimaryBlue.copy(alpha = 0.12f), modifier = Modifier.size(42.dp)) { - Box(contentAlignment = Alignment.Center) { Icon(Icons.Outlined.BluetoothSearching, null, tint = PrimaryBlue) } + ExpressiveIconSurface(size = 42.dp, containerColor = MaterialTheme.colorScheme.primaryContainer) { + Box(contentAlignment = Alignment.Center) { Icon(Icons.AutoMirrored.Outlined.BluetoothSearching, null, tint = MaterialTheme.colorScheme.primary) } } Column(Modifier.weight(1f)) { - Text("Nearby friend-code exchange", fontWeight = FontWeight.Bold, color = DarkText) - Text("Broadcast your maimai friend code and player name over BLE to discover nearby Maimai players on Derakkuma.", fontSize = 12.sp, color = MutedText, lineHeight = 16.sp) + Text("Nearby friend-code exchange", fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) + Text("Broadcast your maimai friend code and player name over BLE to discover nearby Maimai players on Derakkuma.", fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant, lineHeight = 16.sp) } } - if (!manager.isSupported()) Text("BLE advertising/scanning is not available on this device.", color = DangerRed, fontSize = 12.sp) - if (!canBubble) Text("Open Home/Friends once first so Derakkuma can cache your player name and friend code.", color = MutedText, fontSize = 12.sp) - message?.let { Text(it, color = MutedText, fontSize = 12.sp) } - Button( + if (!manager.isSupported()) Text("BLE advertising/scanning is not available on this device.", color = MaterialTheme.colorScheme.error, fontSize = 12.sp) + if (!canBubble) Text("Open Home/Friends once first so Derakkuma can cache your player name and friend code.", color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = 12.sp) + message?.let { Text(it, color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = 12.sp) } + ExpressiveButton( enabled = manager.isSupported() && canBubble, onClick = { scope.launch { @@ -70,15 +73,15 @@ fun BubbleTab(client: MaimaiClient, cookie: String, cacheStore: CacheStore) { } } }, - colors = ButtonDefaults.buttonColors(containerColor = if (broadcasting) DangerRed else PrimaryBlue), + colors = ButtonDefaults.buttonColors(containerColor = if (broadcasting) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.primary), modifier = Modifier.fillMaxWidth(), ) { Text(if (broadcasting) "Stop Bubble" else "Start Bubble") } } } - Text("Nearby", fontWeight = FontWeight.Bold, color = DarkText) + Text("Nearby", fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) if (peers.isEmpty()) { - Text(if (broadcasting) "Looking for nearby players..." else "Start Bubble to discover nearby players.", color = MutedText, fontSize = 13.sp) + Text(if (broadcasting) "Looking for nearby players..." else "Start Bubble to discover nearby players.", color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = 13.sp) } LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp), contentPadding = PaddingValues(bottom = 96.dp)) { items(peers, key = { it.id }) { peer -> @@ -120,16 +123,16 @@ fun BubbleTab(client: MaimaiClient, cookie: String, cacheStore: CacheStore) { @Composable private fun BubblePeerRow(peer: BubblePeer, busy: Boolean, onRequest: () -> Unit) { - Card(colors = CardDefaults.cardColors(containerColor = CardWhite), shape = RoundedCornerShape(12.dp)) { + ExpressiveGlassCard(cornerRadius = 24.dp) { Row(Modifier.fillMaxWidth().padding(12.dp), horizontalArrangement = Arrangement.spacedBy(10.dp), verticalAlignment = Alignment.CenterVertically) { MaimaiRemoteImage(peer.iconUrl, modifier = Modifier.size(42.dp), contentDescription = null) Column(Modifier.weight(1f)) { - Text(peer.playerName.ifBlank { "Nearby player" }, fontWeight = FontWeight.Bold, color = DarkText) - Text("${peer.friendCode} · ${signalLabel(peer.rssi)}", fontSize = 12.sp, color = MutedText) - if (peer.comment.isNotBlank()) Text(peer.comment, fontSize = 11.sp, color = MutedText, maxLines = 2) + Text(peer.playerName.ifBlank { "Nearby player" }, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) + Text("${peer.friendCode} · ${signalLabel(peer.rssi)}", fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) + if (peer.comment.isNotBlank()) Text(peer.comment, fontSize = 11.sp, color = MaterialTheme.colorScheme.onSurfaceVariant, maxLines = 2) } - Button(onClick = onRequest, enabled = !busy, colors = ButtonDefaults.buttonColors(containerColor = PrimaryBlue)) { - if (busy) CircularProgressIndicator(Modifier.size(16.dp), strokeWidth = 2.dp, color = CardWhite) else Icon(Icons.Outlined.PersonAdd, null, modifier = Modifier.size(18.dp)) + ExpressiveButton(onClick = onRequest, enabled = !busy, colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary)) { + if (busy) ExpressiveLoadingIndicator(modifier = Modifier, color = MaterialTheme.colorScheme.onPrimary, size = 16.dp) else Icon(Icons.Outlined.PersonAdd, null, modifier = Modifier.size(18.dp)) Spacer(Modifier.width(6.dp)) Text("Request") } diff --git a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/CollectionTab.kt b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/CollectionTab.kt index 67bed6e..05941d7 100644 --- a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/CollectionTab.kt +++ b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/CollectionTab.kt @@ -1,10 +1,10 @@ package com.derakkuma.ui.tabs +import com.derakkuma.ui.components.ExpressiveLoadingIndicator import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.* import androidx.compose.material3.* @@ -31,14 +31,14 @@ import com.derakkuma.data.TourMemberCandidate import com.derakkuma.data.TourMemberFormationPage import com.derakkuma.data.TourMemberSelectionPage import com.derakkuma.ui.components.EmptyState +import com.derakkuma.ui.components.ExpressiveButton +import com.derakkuma.ui.components.ExpressiveGlassCard +import com.derakkuma.ui.components.ExpressiveFilterChip +import com.derakkuma.ui.components.ExpressiveTextButton import com.derakkuma.ui.components.LoadingState import com.derakkuma.ui.images.BundledIcon import com.derakkuma.ui.images.BundledImage import com.derakkuma.ui.images.MaimaiRemoteImage -import com.derakkuma.ui.theme.CardWhite -import com.derakkuma.ui.theme.DarkText -import com.derakkuma.ui.theme.MutedText -import com.derakkuma.ui.theme.PrimaryBlue import kotlinx.coroutines.launch @Composable @@ -92,10 +92,10 @@ fun CollectionTab( } Column(Modifier.fillMaxSize().padding(start = 12.dp, end = 12.dp, top = 12.dp)) { - Text("Collection", fontSize = 20.sp, fontWeight = FontWeight.Bold, color = DarkText, modifier = Modifier.padding(bottom = 8.dp)) + Text("Collection", fontSize = 20.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface, modifier = Modifier.padding(bottom = 8.dp)) LazyRow(horizontalArrangement = Arrangement.spacedBy(2.dp), contentPadding = PaddingValues(0.dp), modifier = Modifier.fillMaxWidth()) { items(CollectionKind.entries) { tab -> - TextButton( + ExpressiveTextButton( onClick = { kind = tab genre = "0" @@ -109,7 +109,7 @@ fun CollectionTab( contentPadding = PaddingValues(horizontal = 6.dp, vertical = 4.dp), modifier = Modifier.height(32.dp), ) { - Text(tab.label, fontSize = 11.sp, fontWeight = if (kind == tab) FontWeight.Bold else FontWeight.Normal, color = Color.White, maxLines = 1) + Text(tab.label, fontSize = 11.sp, fontWeight = if (kind == tab) FontWeight.Bold else FontWeight.Normal, color = MaterialTheme.colorScheme.onPrimary, maxLines = 1) } } } @@ -118,7 +118,7 @@ fun CollectionTab( return@Column } if (loading) { - LoadingState(Modifier.fillMaxSize(), color = PrimaryBlue) + LoadingState(Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.primary) return@Column } page?.let { data -> @@ -155,7 +155,7 @@ fun CollectionTab( } } if (kind != CollectionKind.TITLE && kind != CollectionKind.TOUR_MEMBER && data.genres.isNotEmpty()) item { GenreSelector(data.genres, genre) { genre = it } } - message?.let { item { Text(it, color = MutedText, fontSize = 12.sp) } } + message?.let { item { Text(it, color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = 12.sp) } } if (kind == CollectionKind.TOUR_MEMBER && showFormation) { arrangement?.let { edited -> item { @@ -282,7 +282,7 @@ fun CollectionTab( return@LazyColumn } visibleSections.forEach { section -> - item { Text(section.title, color = DarkText, fontWeight = FontWeight.Bold, fontSize = 15.sp, modifier = Modifier.padding(top = 8.dp)) } + item { Text(section.title, color = MaterialTheme.colorScheme.onSurface, fontWeight = FontWeight.Bold, fontSize = 15.sp, modifier = Modifier.padding(top = 8.dp)) } items(section.items.size, key = { index -> "${kind.name}:${section.id}:$index:${section.items[index].name}" }) { index -> val item = section.items[index] CollectionItemCard( @@ -310,19 +310,19 @@ fun CollectionTab( } if (!loading && visibleSections.isEmpty()) { item { - Text("No ${kind.label.lowercase()} entries found.", color = MutedText, modifier = Modifier.padding(12.dp)) + Text("No ${kind.label.lowercase()} entries found.", color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.padding(12.dp)) } } } } ?: Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - if (loading) CircularProgressIndicator() else Text(message ?: "No collection data", color = MutedText) + if (loading) ExpressiveLoadingIndicator() else Text(message ?: "No collection data", color = MaterialTheme.colorScheme.onSurfaceVariant) } } } @Composable private fun CurrentCollectionCard(item: CollectionItem?, page: CollectionPage) { - Card(colors = CardDefaults.cardColors(containerColor = CardWhite), shape = RoundedCornerShape(12.dp)) { + ExpressiveGlassCard(cornerRadius = 24.dp) { Row(Modifier.fillMaxWidth().padding(12.dp), horizontalArrangement = Arrangement.spacedBy(10.dp), verticalAlignment = Alignment.CenterVertically) { if (page.kind == CollectionKind.NAMEPLATE || page.kind == CollectionKind.FRAME) { MaimaiRemoteImage(item?.imageUrl, modifier = Modifier.width(116.dp).height(42.dp), contentDescription = null, contentScale = ContentScale.Fit) @@ -330,10 +330,10 @@ private fun CurrentCollectionCard(item: CollectionItem?, page: CollectionPage) { MaimaiRemoteImage(item?.imageUrl, modifier = Modifier.size(58.dp), contentDescription = null) } Column(Modifier.weight(1f)) { - Text("Currently using", color = MutedText, fontSize = 11.sp) - Text(item?.name ?: "Unknown", color = DarkText, fontWeight = FontWeight.Bold) - item?.category?.takeIf { it.isNotBlank() }?.let { Text(it, color = MutedText, fontSize = 12.sp) } - Text("Favorite ${page.favoriteCount}/${page.favoriteTotal} · Total ${page.itemCount}", color = MutedText, fontSize = 12.sp) + Text("Currently using", color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = 11.sp) + Text(item?.name ?: "Unknown", color = MaterialTheme.colorScheme.onSurface, fontWeight = FontWeight.Bold) + item?.category?.takeIf { it.isNotBlank() }?.let { Text(it, color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = 12.sp) } + Text("Favorite ${page.favoriteCount}/${page.favoriteTotal} · Total ${page.itemCount}", color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = 12.sp) } } } @@ -342,7 +342,7 @@ private fun CurrentCollectionCard(item: CollectionItem?, page: CollectionPage) { @Composable private fun GenreSelector(genres: List, selected: String, onSelect: (String) -> Unit) { LazyRow(horizontalArrangement = Arrangement.spacedBy(6.dp), modifier = Modifier.fillMaxWidth()) { - items(genres) { g -> CollectionFilterChip(selected = selected == g.value, onClick = { onSelect(g.value) }) { Text(g.label, fontSize = 11.sp) } } + items(genres) { g -> CollectionExpressiveFilterChip(selected = selected == g.value, onClick = { onSelect(g.value) }) { Text(g.label, fontSize = 11.sp) } } } } @@ -350,29 +350,17 @@ private fun GenreSelector(genres: List, selected: String, onSel private fun RareSelector(selected: String, onSelect: (String) -> Unit) { Row(horizontalArrangement = Arrangement.spacedBy(6.dp), modifier = Modifier.fillMaxWidth()) { listOf("0" to "Normal", "1" to "Bronze", "2" to "Silver", "3" to "Gold", "4" to "Rainbow").forEach { (value, label) -> - CollectionFilterChip(selected = selected == value, onClick = { onSelect(value) }) { Text(label, fontSize = 11.sp) } + CollectionExpressiveFilterChip(selected = selected == value, onClick = { onSelect(value) }) { Text(label, fontSize = 11.sp) } } } } @Composable -private fun CollectionFilterChip(selected: Boolean, onClick: () -> Unit, label: @Composable () -> Unit) { - FilterChip( +private fun CollectionExpressiveFilterChip(selected: Boolean, onClick: () -> Unit, label: @Composable () -> Unit) { + ExpressiveFilterChip( selected = selected, onClick = onClick, label = label, - colors = FilterChipDefaults.filterChipColors( - containerColor = Color.White, - labelColor = DarkText, - selectedContainerColor = PrimaryBlue, - selectedLabelColor = Color.White, - ), - border = FilterChipDefaults.filterChipBorder( - enabled = true, - selected = selected, - borderColor = Color.Transparent, - selectedBorderColor = PrimaryBlue, - ), ) } @@ -380,16 +368,16 @@ private fun CollectionFilterChip(selected: Boolean, onClick: () -> Unit, label: private fun TourMemberControls(page: CollectionPage, selected: String, showingFormation: Boolean, onSelect: (String) -> Unit, onFormation: () -> Unit) { Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { if (page.joinedMembers.isNotBlank() || page.starCounts.isNotEmpty()) { - Card(colors = CardDefaults.cardColors(containerColor = CardWhite), shape = RoundedCornerShape(10.dp)) { + ExpressiveGlassCard(cornerRadius = 22.dp) { Column(Modifier.fillMaxWidth().padding(10.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { - if (page.joinedMembers.isNotBlank()) Text("${page.joinedMembers.filter { it.isDigit() }} partners", color = DarkText, fontWeight = FontWeight.Bold, fontSize = 13.sp) + if (page.joinedMembers.isNotBlank()) Text("${page.joinedMembers.filter { it.isDigit() }} partners", color = MaterialTheme.colorScheme.onSurface, fontWeight = FontWeight.Bold, fontSize = 13.sp) if (page.starCounts.isNotEmpty()) { Row(horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) { page.starCounts.forEach { (stars, count) -> Row(horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.CenterVertically) { - Text("$stars", color = MutedText, fontSize = 11.sp) + Text("$stars", color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = 11.sp) BundledIcon(BundledImage.IconStar, modifier = Modifier.size(12.dp), contentDescription = "star") - Text("×$count", color = MutedText, fontSize = 11.sp) + Text("×$count", color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = 11.sp) } } } @@ -398,13 +386,12 @@ private fun TourMemberControls(page: CollectionPage, selected: String, showingFo } } Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { - CollectionFilterChip(selected = selected != "event", onClick = { onSelect("0") }) { Text("Area") } - CollectionFilterChip(selected = selected == "event", onClick = { onSelect("event") }) { Text("Event Area") } + CollectionExpressiveFilterChip(selected = selected != "event", onClick = { onSelect("0") }) { Text("Area") } + CollectionExpressiveFilterChip(selected = selected == "event", onClick = { onSelect("event") }) { Text("Event Area") } } - Button( + ExpressiveButton( onClick = onFormation, - shape = RoundedCornerShape(999.dp), - colors = ButtonDefaults.buttonColors(containerColor = PrimaryBlue, contentColor = Color.White), + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary, contentColor = MaterialTheme.colorScheme.onPrimary), contentPadding = PaddingValues(horizontal = 14.dp, vertical = 8.dp), ) { Icon(Icons.Outlined.Groups, null, modifier = Modifier.size(18.dp)) @@ -438,16 +425,16 @@ private fun FormationCard( SelectionEditor(selection = sel, onSelectCandidate = onSelectCandidate) return@Column } - Text("Tour Members Formation", color = DarkText, fontWeight = FontWeight.Bold, fontSize = 15.sp) + Text("Tour Members Formation", color = MaterialTheme.colorScheme.onSurface, fontWeight = FontWeight.Bold, fontSize = 15.sp) page.slots.groupBy { it.preset }.forEach { (preset, slots) -> - Card(colors = CardDefaults.cardColors(containerColor = CardWhite), shape = RoundedCornerShape(10.dp)) { + ExpressiveGlassCard(cornerRadius = 24.dp) { Column(Modifier.fillMaxWidth().padding(10.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) { - Text(if (preset == 0) "Current Formation" else "Preset $preset", color = DarkText, fontWeight = FontWeight.Bold) + Text(if (preset == 0) "Current Formation" else "Preset $preset", color = MaterialTheme.colorScheme.onSurface, fontWeight = FontWeight.Bold) if (preset > 0) { Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) { - TextButton(onClick = { onArrangePreset(preset) }, contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp)) { Text("Arrange", fontSize = 11.sp) } - Button(onClick = { onSetPreset(preset) }, contentPadding = PaddingValues(horizontal = 10.dp, vertical = 4.dp)) { Text("Use", fontSize = 11.sp) } + ExpressiveTextButton(onClick = { onArrangePreset(preset) }, contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp)) { Text("Arrange", fontSize = 11.sp) } + ExpressiveButton(onClick = { onSetPreset(preset) }, contentPadding = PaddingValues(horizontal = 10.dp, vertical = 4.dp)) { Text("Use", fontSize = 11.sp) } } } } @@ -456,8 +443,8 @@ private fun FormationCard( Column(Modifier.weight(1f), horizontalAlignment = Alignment.CenterHorizontally) { MaimaiRemoteImage(slot.imageUrl, modifier = Modifier.size(48.dp), contentDescription = null) TourMemberMeta(level = slot.level, awakening = slot.awakening, fontSize = 10) - if (slot.locked) Text("Lock", color = MutedText, fontSize = 9.sp) - TextButton(onClick = { onChangeSlot(preset, slot.position) }, contentPadding = PaddingValues(0.dp)) { Text("Change", fontSize = 10.sp) } + if (slot.locked) Text("Lock", color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = 9.sp) + ExpressiveTextButton(onClick = { onChangeSlot(preset, slot.position) }, contentPadding = PaddingValues(0.dp)) { Text("Change", fontSize = 10.sp) } } } } @@ -469,16 +456,16 @@ private fun FormationCard( @Composable private fun SelectionEditor(selection: TourMemberSelectionPage, onSelectCandidate: (TourMemberCandidate) -> Unit) { - Text("Choose member for Preset ${selection.preset} Slot ${selection.position}", color = DarkText, fontWeight = FontWeight.Bold, fontSize = 14.sp) + Text("Choose member for Preset ${selection.preset} Slot ${selection.position}", color = MaterialTheme.colorScheme.onSurface, fontWeight = FontWeight.Bold, fontSize = 14.sp) selection.candidates.forEach { candidate -> - Card(colors = CardDefaults.cardColors(containerColor = CardWhite), shape = RoundedCornerShape(10.dp)) { + ExpressiveGlassCard(cornerRadius = 22.dp) { Row(Modifier.fillMaxWidth().padding(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) { MaimaiRemoteImage(candidate.imageUrl, modifier = Modifier.size(44.dp), contentDescription = null) Column(Modifier.weight(1f)) { - Text(candidate.name, color = DarkText, fontWeight = FontWeight.Bold, fontSize = 13.sp) + Text(candidate.name, color = MaterialTheme.colorScheme.onSurface, fontWeight = FontWeight.Bold, fontSize = 13.sp) TourMemberMeta(level = candidate.level, awakening = candidate.awakening, fontSize = 11) } - Button(onClick = { onSelectCandidate(candidate) }, contentPadding = PaddingValues(horizontal = 10.dp, vertical = 4.dp)) { Text("Set", fontSize = 11.sp) } + ExpressiveButton(onClick = { onSelectCandidate(candidate) }, contentPadding = PaddingValues(horizontal = 10.dp, vertical = 4.dp)) { Text("Set", fontSize = 11.sp) } } } } @@ -486,23 +473,23 @@ private fun SelectionEditor(selection: TourMemberSelectionPage, onSelectCandidat @Composable private fun ArrangementEditor(page: TourMemberArrangementPage, onChange: (TourMemberArrangementPage) -> Unit, onSave: () -> Unit) { - Text("Arrange Preset ${page.preset}", color = DarkText, fontWeight = FontWeight.Bold, fontSize = 15.sp) - Text("Use ↑/↓ to reorder members, then save.", color = MutedText, fontSize = 12.sp) - Card(colors = CardDefaults.cardColors(containerColor = CardWhite), shape = RoundedCornerShape(10.dp)) { + Text("Arrange Preset ${page.preset}", color = MaterialTheme.colorScheme.onSurface, fontWeight = FontWeight.Bold, fontSize = 15.sp) + Text("Use ↑/↓ to reorder members, then save.", color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = 12.sp) + ExpressiveGlassCard(cornerRadius = 24.dp) { Column(Modifier.fillMaxWidth().padding(10.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { page.slots.sortedBy { it.position }.forEach { slot -> Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) { MaimaiRemoteImage(slot.imageUrl, modifier = Modifier.size(42.dp), contentDescription = null) Column(Modifier.weight(1f)) { - Text("Slot ${slot.position}", color = DarkText, fontWeight = FontWeight.Bold, fontSize = 12.sp) + Text("Slot ${slot.position}", color = MaterialTheme.colorScheme.onSurface, fontWeight = FontWeight.Bold, fontSize = 12.sp) TourMemberMeta(level = slot.level, awakening = slot.awakening, fontSize = 10) } - TextButton(enabled = slot.position > 1, onClick = { onChange(page.copy(slots = page.slots.swapPositions(slot.position, slot.position - 1))) }) { Text("↑", fontSize = 16.sp) } - TextButton(enabled = slot.position < 5, onClick = { onChange(page.copy(slots = page.slots.swapPositions(slot.position, slot.position + 1))) }) { Text("↓", fontSize = 16.sp) } - FilterChip(selected = slot.lock == "1", onClick = { onChange(page.copy(slots = page.slots.map { if (it.position == slot.position) it.copy(lock = if (it.lock == "1") "0" else "1") else it })) }, label = { Text("Lock", fontSize = 10.sp) }) + ExpressiveTextButton(enabled = slot.position > 1, onClick = { onChange(page.copy(slots = page.slots.swapPositions(slot.position, slot.position - 1))) }) { Text("↑", fontSize = 16.sp) } + ExpressiveTextButton(enabled = slot.position < 5, onClick = { onChange(page.copy(slots = page.slots.swapPositions(slot.position, slot.position + 1))) }) { Text("↓", fontSize = 16.sp) } + CollectionExpressiveFilterChip(selected = slot.lock == "1", onClick = { onChange(page.copy(slots = page.slots.map { if (it.position == slot.position) it.copy(lock = if (it.lock == "1") "0" else "1") else it })) }) { Text("Lock", fontSize = 10.sp) } } } - Button(onClick = onSave, modifier = Modifier.align(Alignment.End)) { Text("Save arrangement") } + ExpressiveButton(onClick = onSave, modifier = Modifier.align(Alignment.End)) { Text("Save arrangement") } } } } @@ -510,10 +497,10 @@ private fun ArrangementEditor(page: TourMemberArrangementPage, onChange: (TourMe @Composable private fun TourMemberMeta(level: String, awakening: String, fontSize: Int) { Row(horizontalArrangement = Arrangement.spacedBy(3.dp), verticalAlignment = Alignment.CenterVertically) { - if (level.isNotBlank()) Text(level, color = MutedText, fontSize = fontSize.sp) + if (level.isNotBlank()) Text(level, color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = fontSize.sp) if (awakening.isNotBlank()) { BundledIcon(BundledImage.IconStar, modifier = Modifier.size((fontSize + 2).dp), contentDescription = "star") - Text(awakening, color = MutedText, fontSize = fontSize.sp) + Text(awakening, color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = fontSize.sp) } } } @@ -536,19 +523,19 @@ private fun CollectionItemCard(item: CollectionItem, kind: CollectionKind, onSet TitleCollectionItemCard(item = item, onSet = onSet, onFavorite = onFavorite) return } - Card(colors = CardDefaults.cardColors(containerColor = CardWhite), shape = RoundedCornerShape(10.dp)) { + ExpressiveGlassCard(cornerRadius = 22.dp) { Row(Modifier.fillMaxWidth().padding(10.dp), horizontalArrangement = Arrangement.spacedBy(10.dp), verticalAlignment = Alignment.CenterVertically) { MaimaiRemoteImage(item.imageUrl, modifier = Modifier.size(if (kind == CollectionKind.FRAME) 72.dp else 54.dp), contentDescription = null) Column(Modifier.weight(1f)) { - Text(item.name, color = if (item.isLocked) MutedText else DarkText, fontWeight = FontWeight.Bold, fontSize = 14.sp) + Text(item.name, color = if (item.isLocked) MaterialTheme.colorScheme.onSurfaceVariant else MaterialTheme.colorScheme.onSurface, fontWeight = FontWeight.Bold, fontSize = 14.sp) val meta = listOf(item.category, item.level).filter { it.isNotBlank() }.joinToString(" · ") - if (meta.isNotBlank()) Text(meta, color = MutedText, fontSize = 11.sp) + if (meta.isNotBlank()) Text(meta, color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = 11.sp) if (item.awakening.isNotBlank()) TourMemberMeta(level = "", awakening = item.awakening, fontSize = 11) - if (item.condition.isNotBlank()) Text(item.condition, color = MutedText, fontSize = 11.sp, maxLines = 2) + if (item.condition.isNotBlank()) Text(item.condition, color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = 11.sp, maxLines = 2) } Column(verticalArrangement = Arrangement.spacedBy(4.dp), horizontalAlignment = Alignment.End) { - if (item.favoriteAction.isNotBlank()) TextButton(onClick = onFavorite) { Text(if (item.favoriteAction.contains("Off")) "Unfav" else "Fav", fontSize = 11.sp) } - if (item.setAction.isNotBlank()) Button(onClick = onSet, contentPadding = PaddingValues(horizontal = 10.dp, vertical = 4.dp)) { Text("Set", fontSize = 11.sp) } + if (item.favoriteAction.isNotBlank()) ExpressiveTextButton(onClick = onFavorite) { Text(if (item.favoriteAction.contains("Off")) "Unfav" else "Fav", fontSize = 11.sp) } + if (item.setAction.isNotBlank()) ExpressiveButton(onClick = onSet, contentPadding = PaddingValues(horizontal = 10.dp, vertical = 4.dp)) { Text("Set", fontSize = 11.sp) } } } } @@ -556,18 +543,18 @@ private fun CollectionItemCard(item: CollectionItem, kind: CollectionKind, onSet @Composable private fun WideCollectionItemCard(item: CollectionItem, onSet: () -> Unit, onFavorite: () -> Unit) { - Card(colors = CardDefaults.cardColors(containerColor = CardWhite), shape = RoundedCornerShape(10.dp)) { + ExpressiveGlassCard(cornerRadius = 24.dp) { Column(Modifier.fillMaxWidth().padding(10.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { MaimaiRemoteImage(item.imageUrl, modifier = Modifier.fillMaxWidth().height(86.dp), contentDescription = null, contentScale = ContentScale.Fit) Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(10.dp), verticalAlignment = Alignment.CenterVertically) { Column(Modifier.weight(1f)) { - Text(item.name, color = if (item.isLocked) MutedText else DarkText, fontWeight = FontWeight.Bold, fontSize = 14.sp) - if (item.category.isNotBlank()) Text(item.category, color = MutedText, fontSize = 11.sp) - if (item.condition.isNotBlank()) Text(item.condition, color = MutedText, fontSize = 11.sp, maxLines = 2) + Text(item.name, color = if (item.isLocked) MaterialTheme.colorScheme.onSurfaceVariant else MaterialTheme.colorScheme.onSurface, fontWeight = FontWeight.Bold, fontSize = 14.sp) + if (item.category.isNotBlank()) Text(item.category, color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = 11.sp) + if (item.condition.isNotBlank()) Text(item.condition, color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = 11.sp, maxLines = 2) } Column(verticalArrangement = Arrangement.spacedBy(4.dp), horizontalAlignment = Alignment.End) { - if (item.favoriteAction.isNotBlank()) TextButton(onClick = onFavorite) { Text(if (item.favoriteAction.contains("Off")) "Unfav" else "Fav", fontSize = 11.sp) } - if (item.setAction.isNotBlank()) Button(onClick = onSet, contentPadding = PaddingValues(horizontal = 10.dp, vertical = 4.dp)) { Text("Set", fontSize = 11.sp) } + if (item.favoriteAction.isNotBlank()) ExpressiveTextButton(onClick = onFavorite) { Text(if (item.favoriteAction.contains("Off")) "Unfav" else "Fav", fontSize = 11.sp) } + if (item.setAction.isNotBlank()) ExpressiveButton(onClick = onSet, contentPadding = PaddingValues(horizontal = 10.dp, vertical = 4.dp)) { Text("Set", fontSize = 11.sp) } } } } @@ -576,15 +563,15 @@ private fun WideCollectionItemCard(item: CollectionItem, onSet: () -> Unit, onFa @Composable private fun TitleCollectionItemCard(item: CollectionItem, onSet: () -> Unit, onFavorite: () -> Unit) { - Card(colors = CardDefaults.cardColors(containerColor = CardWhite), shape = RoundedCornerShape(10.dp)) { + ExpressiveGlassCard(cornerRadius = 18.dp) { Row(Modifier.fillMaxWidth().padding(horizontal = 10.dp, vertical = 6.dp), horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) { Column(Modifier.weight(1f)) { - Text(item.name, color = if (item.isLocked) MutedText else DarkText, fontWeight = FontWeight.Bold, fontSize = 13.sp, maxLines = 2) - if (item.category.isNotBlank()) Text(item.category, color = MutedText, fontSize = 10.sp, maxLines = 1) + Text(item.name, color = if (item.isLocked) MaterialTheme.colorScheme.onSurfaceVariant else MaterialTheme.colorScheme.onSurface, fontWeight = FontWeight.Bold, fontSize = 13.sp, maxLines = 2) + if (item.category.isNotBlank()) Text(item.category, color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = 10.sp, maxLines = 1) } Row(horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.CenterVertically) { - if (item.favoriteAction.isNotBlank()) TextButton(onClick = onFavorite) { Text(if (item.favoriteAction.contains("Off")) "Unfav" else "Fav", fontSize = 11.sp) } - if (item.setAction.isNotBlank()) Button(onClick = onSet, contentPadding = PaddingValues(horizontal = 10.dp, vertical = 4.dp)) { Text("Set", fontSize = 11.sp) } + if (item.favoriteAction.isNotBlank()) ExpressiveTextButton(onClick = onFavorite) { Text(if (item.favoriteAction.contains("Off")) "Unfav" else "Fav", fontSize = 11.sp) } + if (item.setAction.isNotBlank()) ExpressiveButton(onClick = onSet, contentPadding = PaddingValues(horizontal = 10.dp, vertical = 4.dp)) { Text("Set", fontSize = 11.sp) } } } } diff --git a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/FavoritesTab.kt b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/FavoritesTab.kt index c1dcc47..c82c08d 100644 --- a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/FavoritesTab.kt +++ b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/FavoritesTab.kt @@ -4,12 +4,14 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.filled.Search +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.MaterialShapes import androidx.compose.material3.* +import androidx.compose.material3.toShape import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -22,6 +24,14 @@ import com.derakkuma.atproto.AtProtoClient import com.derakkuma.atproto.syncFavoriteListToAtProto import com.derakkuma.data.* import com.derakkuma.ui.components.EmptyState +import com.derakkuma.ui.components.ExpressiveGlassCard +import com.derakkuma.ui.components.ExpressiveCheckbox +import com.derakkuma.ui.components.ExpressiveLoadingIndicator +import com.derakkuma.ui.components.ExpressiveScaffold +import com.derakkuma.ui.components.ExpressiveSearchBar +import com.derakkuma.ui.components.ExpressiveTextButton +import com.derakkuma.ui.components.ExpressiveSnackbar +import com.derakkuma.ui.components.ExpressiveFloatingActionButton import com.derakkuma.ui.components.LoadingState import com.derakkuma.ui.images.* import com.derakkuma.ui.theme.* @@ -29,6 +39,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable fun FavoritesTab(client: MaimaiClient, cookie: String, maintenanceMode: Boolean = false, cacheStore: CacheStore = CacheStore(), atProtoClient: AtProtoClient? = null) { var favorites by remember { mutableStateOf(null) } @@ -60,22 +71,21 @@ fun FavoritesTab(client: MaimaiClient, cookie: String, maintenanceMode: Boolean loading = false } - Scaffold( + ExpressiveScaffold( contentWindowInsets = WindowInsets(0.dp), snackbarHost = { if (snackMsg != null) { - Snackbar( + ExpressiveSnackbar( modifier = Modifier.padding(12.dp), - shape = RoundedCornerShape(8.dp), - action = { TextButton(onClick = { snackMsg = null }) { Text("OK") } }, + action = { ExpressiveTextButton(onClick = { snackMsg = null }) { Text("OK") } }, ) { Text(snackMsg!!) } } }, floatingActionButton = { if (favorites != null && !loading && !maintenanceMode) { - FloatingActionButton( + ExpressiveFloatingActionButton( onClick = { - if (loadingEdit) return@FloatingActionButton + if (loadingEdit) return@ExpressiveFloatingActionButton if (editing) { // Save val selectedIds = checkedSongs.filter { it.value }.keys.toList() @@ -118,15 +128,15 @@ fun FavoritesTab(client: MaimaiClient, cookie: String, maintenanceMode: Boolean } } }, - containerColor = PrimaryBlue, + containerColor = MaterialTheme.colorScheme.primary, ) { if (saving || loadingEdit) { - CircularProgressIndicator(modifier = Modifier.size(24.dp), color = CardWhite, strokeWidth = 2.dp) + ExpressiveLoadingIndicator(modifier = Modifier, color = MaterialTheme.colorScheme.onPrimary, size = 24.dp) } else { Icon( if (editing) Icons.Default.Edit else Icons.Default.Edit, contentDescription = if (editing) "Save" else "Edit", - tint = CardWhite, + tint = MaterialTheme.colorScheme.onPrimary, ) } } @@ -140,23 +150,27 @@ fun FavoritesTab(client: MaimaiClient, cookie: String, maintenanceMode: Boolean Column(Modifier.fillMaxSize().padding(padding)) { // Header + search Column(modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp)) { - Text("Edit Favorites", fontSize = 20.sp, fontWeight = FontWeight.Bold, color = DarkText) + Text("Edit Favorites", fontSize = 20.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) Spacer(Modifier.height(4.dp)) - Text("Select songs to favorite", fontSize = 12.sp, color = MutedText) + Text("Select songs to favorite", fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) Spacer(Modifier.height(8.dp)) - OutlinedTextField( - value = searchQuery, - onValueChange = { searchQuery = it }, + var searchActive by remember { mutableStateOf(false) } + ExpressiveSearchBar( + inputField = { + SearchBarDefaults.InputField( + query = searchQuery, + onQueryChange = { searchQuery = it }, + onSearch = { searchActive = false }, + expanded = searchActive, + onExpandedChange = { searchActive = it }, + placeholder = { Text("Search songs...") }, + leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) }, + ) + }, + expanded = searchActive, + onExpandedChange = { searchActive = it }, modifier = Modifier.fillMaxWidth(), - placeholder = { Text("Search songs...") }, - leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) }, - singleLine = true, - shape = RoundedCornerShape(8.dp), - colors = OutlinedTextFieldDefaults.colors( - unfocusedContainerColor = CardWhite, - focusedContainerColor = CardWhite, - ), - ) + ) {} Spacer(Modifier.height(8.dp)) } @@ -170,10 +184,9 @@ fun FavoritesTab(client: MaimaiClient, cookie: String, maintenanceMode: Boolean } } - Card( + ExpressiveGlassCard( modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp).weight(1f), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), + cornerRadius = 24.dp, ) { LazyColumn( modifier = Modifier.fillMaxSize(), @@ -185,24 +198,23 @@ fun FavoritesTab(client: MaimaiClient, cookie: String, maintenanceMode: Boolean verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp), ) { - Checkbox( + ExpressiveCheckbox( checked = checkedSongs[song.value] ?: false, onCheckedChange = { checked -> checkedSongs = checkedSongs.toMutableMap().apply { this[song.value] = checked } }, - colors = CheckboxDefaults.colors(checkedColor = PrimaryBlue), ) if (song.jacketUrl.isNotBlank()) { MaimaiRemoteImage( url = song.jacketUrl, - modifier = Modifier.size(36.dp).clip(RoundedCornerShape(4.dp)), + modifier = Modifier.size(36.dp).clip(MaterialShapes.Square.toShape()), contentDescription = song.name, contentScale = ContentScale.Crop, ) } - Text(song.name, fontSize = 13.sp, color = DarkText, modifier = Modifier.weight(1f)) + Text(song.name, fontSize = 13.sp, color = MaterialTheme.colorScheme.onSurface, modifier = Modifier.weight(1f)) if (song.genre.isNotBlank()) { - Text(song.genre, fontSize = 10.sp, color = MutedText) + Text(song.genre, fontSize = 10.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } } } @@ -210,7 +222,7 @@ fun FavoritesTab(client: MaimaiClient, cookie: String, maintenanceMode: Boolean if (filteredSongs.isEmpty()) { item { Box(Modifier.fillMaxWidth().padding(24.dp), contentAlignment = Alignment.Center) { - Text("No songs match search", fontSize = 14.sp, color = MutedText) + Text("No songs match search", fontSize = 14.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } } } @@ -226,18 +238,17 @@ fun FavoritesTab(client: MaimaiClient, cookie: String, maintenanceMode: Boolean modifier = Modifier.fillMaxSize().padding(padding).verticalScroll(rememberScrollState()).padding(start = 12.dp, end = 12.dp, bottom = 88.dp), ) { // Header - Card( + ExpressiveGlassCard( modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), + cornerRadius = 24.dp, ) { Row( modifier = Modifier.padding(16.dp).fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { - Text("Favorites", fontSize = 20.sp, fontWeight = FontWeight.Bold, color = DarkText) - Text("${fav.current} / ${fav.max}", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = PrimaryBlue) + Text("Favorites", fontSize = 20.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) + Text("${fav.current} / ${fav.max}", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.primary) } } @@ -247,13 +258,12 @@ fun FavoritesTab(client: MaimaiClient, cookie: String, maintenanceMode: Boolean val songsByGenre = fav.songs.groupBy { it.genre } songsByGenre.forEach { (genre, songs) -> if (genre.isNotBlank()) { - Text(genre, fontSize = 14.sp, fontWeight = FontWeight.Bold, color = PrimaryBlue, modifier = Modifier.padding(start = 4.dp, top = 8.dp)) + Text(genre, fontSize = 14.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.primary, modifier = Modifier.padding(start = 4.dp, top = 8.dp)) } songs.forEach { song -> - Card( + ExpressiveGlassCard( modifier = Modifier.fillMaxWidth().padding(vertical = 3.dp), - shape = RoundedCornerShape(6.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), + cornerRadius = 16.dp, ) { Row( modifier = Modifier.padding(8.dp).fillMaxWidth(), @@ -263,15 +273,15 @@ fun FavoritesTab(client: MaimaiClient, cookie: String, maintenanceMode: Boolean if (song.jacketUrl.isNotBlank()) { MaimaiRemoteImage( url = song.jacketUrl, - modifier = Modifier.size(44.dp).clip(RoundedCornerShape(4.dp)), + modifier = Modifier.size(44.dp).clip(MaterialShapes.Square.toShape()), contentDescription = song.name, contentScale = ContentScale.Crop, ) } Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(2.dp)) { - Text(song.name, fontSize = 13.sp, fontWeight = FontWeight.SemiBold, color = DarkText, maxLines = 1) + Text(song.name, fontSize = 13.sp, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.onSurface, maxLines = 1) if (song.artist.isNotBlank()) { - Text(song.artist, fontSize = 11.sp, color = MutedText, maxLines = 1) + Text(song.artist, fontSize = 11.sp, color = MaterialTheme.colorScheme.onSurfaceVariant, maxLines = 1) } } if (song.isDx) { @@ -283,13 +293,12 @@ fun FavoritesTab(client: MaimaiClient, cookie: String, maintenanceMode: Boolean } if (fav.songs.isEmpty()) { - Card( + ExpressiveGlassCard( modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), + cornerRadius = 24.dp, ) { Box(Modifier.fillMaxWidth().padding(24.dp), contentAlignment = Alignment.Center) { - Text("No favorites yet", fontSize = 14.sp, color = MutedText) + Text("No favorites yet", fontSize = 14.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } } } diff --git a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/FriendsTab.kt b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/FriendsTab.kt index fa7a1f5..6d50bfa 100644 --- a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/FriendsTab.kt +++ b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/FriendsTab.kt @@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.QrCode @@ -16,7 +15,6 @@ import androidx.compose.material.icons.filled.QrCodeScanner import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Star import androidx.compose.material3.* -import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -42,6 +40,16 @@ import com.derakkuma.friends.FriendScanDeepLink import com.derakkuma.friends.friendScanCode import com.derakkuma.friends.friendScanUrl import com.derakkuma.ui.components.EmptyCard +import com.derakkuma.ui.components.ExpressiveAssistChip +import com.derakkuma.ui.components.ExpressiveButton +import com.derakkuma.ui.components.ExpressiveFloatingActionButton +import com.derakkuma.ui.components.ExpressiveGlassCard +import com.derakkuma.ui.components.ExpressivePullToRefreshBox +import com.derakkuma.ui.components.ExpressiveFilterChip +import com.derakkuma.ui.components.ExpressiveOutlinedButton +import com.derakkuma.ui.components.ExpressiveTextButton +import com.derakkuma.ui.components.ExpressiveAlertDialog +import com.derakkuma.ui.components.ExpressiveTextField import com.derakkuma.ui.components.InfoCard import com.derakkuma.ui.components.MaimaiProfile import com.derakkuma.ui.components.SectionHeader @@ -405,7 +413,7 @@ fun FriendsTab( } Box(Modifier.fillMaxSize()) { - PullToRefreshBox( + ExpressivePullToRefreshBox( isRefreshing = loading, onRefresh = { reload() }, modifier = Modifier.fillMaxSize(), @@ -447,7 +455,7 @@ fun FriendsTab( } } - if (message != null) item { InfoCard(message!!, PrimaryBlue) } + if (message != null) item { InfoCard(message!!, MaterialTheme.colorScheme.primary) } if (error != null) item { InfoCard(error!!, MaterialTheme.colorScheme.error) } when (section) { @@ -605,14 +613,14 @@ fun FriendsTab( circleRewards?.let { page -> if (page.rewards.isNotEmpty()) { item { SectionHeader("Point Rewards") } - if (page.monthlyIconText.isNotBlank()) item { InfoCard(page.monthlyIconText, PrimaryBlue) } + if (page.monthlyIconText.isNotBlank()) item { InfoCard(page.monthlyIconText, MaterialTheme.colorScheme.primary) } items(page.rewards, key = { it.points }) { reward -> RewardRow(reward) } } } } FriendsSection.CircleRanking -> circleRanking?.let { page -> - item { InfoCard("Your circle total: ${page.totalPoints} PT", PrimaryBlue) } + item { InfoCard("Your circle total: ${page.totalPoints} PT", MaterialTheme.colorScheme.primary) } items(page.entries, key = { "circle-rank-${it.rank}-${it.name}-${it.points}" }) { entry -> CircleRankingRow(entry) } } @@ -676,7 +684,7 @@ fun FriendsTab( } } - FloatingActionButton( + ExpressiveFloatingActionButton( onClick = { if (mode == FriendsMode.Circles) { showCircleSearch = true @@ -696,8 +704,8 @@ fun FriendsTab( } }, modifier = Modifier.align(Alignment.BottomEnd).padding(end = 16.dp, bottom = 40.dp), - containerColor = PrimaryBlue, - contentColor = Color.White, + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary, ) { Icon(Icons.Default.Search, contentDescription = "Search") } @@ -706,15 +714,15 @@ fun FriendsTab( @Composable private fun PrimaryTabs(selected: FriendsMode, onSelect: (FriendsMode) -> Unit) { - Card(Modifier.fillMaxWidth(), shape = RoundedCornerShape(8.dp), colors = CardDefaults.cardColors(containerColor = CardWhite)) { + ExpressiveGlassCard(Modifier.fillMaxWidth()) { Row(Modifier.fillMaxWidth().padding(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp)) { FriendsMode.entries.forEach { mode -> - Button( + ExpressiveButton( onClick = { onSelect(mode) }, modifier = Modifier.weight(1f), colors = ButtonDefaults.buttonColors( - containerColor = if (selected == mode) PrimaryBlue else Color(0xFFE5E7EB), - contentColor = if (selected == mode) Color.White else DarkText, + containerColor = if (selected == mode) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surfaceContainerHigh, + contentColor = if (selected == mode) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onSurface, ), ) { Text(mode.label) } } @@ -731,7 +739,7 @@ private fun SectionChips(mode: FriendsMode, selected: FriendsSection, pendingAcc } Row(Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()), horizontalArrangement = Arrangement.spacedBy(6.dp)) { sections.forEach { section -> - FilterChip( + ExpressiveFilterChip( selected = selected == section, onClick = { onSelect(section) }, label = { @@ -740,10 +748,6 @@ private fun SectionChips(mode: FriendsMode, selected: FriendsSection, pendingAcc fontSize = 11.sp, ) }, - colors = FilterChipDefaults.filterChipColors( - containerColor = Color.White.copy(alpha = 0.5f), - selectedContainerColor = Color.White, - ), ) } } @@ -755,19 +759,17 @@ private fun FriendRequestCard( onAccept: () -> Unit, onReject: () -> Unit, ) { - Card( + ExpressiveGlassCard( Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), ) { Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { UserCard(user = user, expanded = true) Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { - Button(onClick = onAccept, enabled = user.idx.isNotBlank() && user.token.isNotBlank()) { Text("Accept") } - OutlinedButton(onClick = onReject, enabled = user.idx.isNotBlank() && user.token.isNotBlank()) { Text("Reject") } + ExpressiveButton(onClick = onAccept, enabled = user.idx.isNotBlank() && user.token.isNotBlank()) { Text("Accept") } + ExpressiveOutlinedButton(onClick = onReject, enabled = user.idx.isNotBlank() && user.token.isNotBlank()) { Text("Reject") } } if (user.idx.isBlank() || user.token.isBlank()) { - Text("Can't act on this request yet — missing maimai form token.", fontSize = 11.sp, color = MutedText) + Text("Can't act on this request yet — missing maimai form token.", fontSize = 11.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } } } @@ -803,44 +805,75 @@ private fun UserCard( ratingTextSize = 12.sp, showComment = expanded, trailingBadges = { - if (user.isFavorite) AssistChip(onClick = {}, label = { Text("Favorite", fontSize = 10.sp) }) - if (user.isRival) AssistChip(onClick = {}, label = { Text("Rival", fontSize = 10.sp) }) + if (user.isFavorite) { + ExpressiveAssistChip( + onClick = {}, + label = { Text("Favorite", fontSize = 10.sp) }, + containerColor = MaterialTheme.colorScheme.tertiaryContainer, + labelColor = MaterialTheme.colorScheme.onTertiaryContainer, + ) + } + if (user.isRival) { + ExpressiveAssistChip( + onClick = {}, + label = { Text("Rival", fontSize = 10.sp) }, + containerColor = MaterialTheme.colorScheme.secondaryContainer, + labelColor = MaterialTheme.colorScheme.onSecondaryContainer, + ) + } }, details = { - if (user.date.isNotBlank()) Text("Date: ${user.date}", fontSize = 11.sp, color = MutedText) - if (user.status.isNotBlank()) Text(user.status, fontSize = 11.sp, color = MutedText) + if (user.date.isNotBlank()) Text("Date: ${user.date}", fontSize = 11.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) + if (user.status.isNotBlank()) Text(user.status, fontSize = 11.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) }, actions = { - onVs?.let { ActionButton("Friend VS", Color(0xFF51BCF3), it) } - onFavorite?.let { ActionButton(if (user.isFavorite) "Unfavorite" else "Favorite", Color(0xFFFA8E53), it) } - onRival?.let { ActionButton(if (user.isRival) "Deregister Rival" else "Register Rival", Color(0xFFFB4B78), it) } - if (primaryActionLabel != null && primaryAction != null) Button(onClick = primaryAction) { Text(primaryActionLabel) } - if (secondaryActionLabel != null && secondaryAction != null) OutlinedButton(onClick = secondaryAction) { Text(secondaryActionLabel) } + onVs?.let { ActionButton("Friend VS", Color(0xFF51BCF3), Color.White, it) } + onFavorite?.let { + ActionButton( + if (user.isFavorite) "Unfavorite" else "Favorite", + if (user.isFavorite) Color(0xFFFA8E53) else Color(0xFFFFD7C4), + if (user.isFavorite) Color.White else Color(0xFF8A3A12), + it, + ) + } + onRival?.let { + ActionButton( + if (user.isRival) "Deregister Rival" else "Register Rival", + if (user.isRival) Color(0xFFFB4B78) else Color(0xFFFFD0DC), + if (user.isRival) Color.White else Color(0xFF8E1234), + it, + ) + } + if (primaryActionLabel != null && primaryAction != null) ExpressiveButton(onClick = primaryAction) { Text(primaryActionLabel) } + if (secondaryActionLabel != null && secondaryAction != null) ExpressiveOutlinedButton(onClick = secondaryAction) { Text(secondaryActionLabel) } }, ) } -@Composable private fun FriendCounts(page: FriendsPage) = InfoCard("Friends ${page.friendCount}/${page.friendMax} \u00B7 Favorites ${page.favoriteCount}/${page.favoriteMax} \u00B7 Rivals ${page.rivalCount}/${page.rivalMax}", PrimaryBlue) +@Composable private fun FriendCounts(page: FriendsPage) = InfoCard("Friends ${page.friendCount}/${page.friendMax} \u00B7 Favorites ${page.favoriteCount}/${page.favoriteMax} \u00B7 Rivals ${page.rivalCount}/${page.rivalMax}", MaterialTheme.colorScheme.primary) @Composable private fun FriendQrActions(onScan: () -> Unit, onMyQr: () -> Unit, scanEnabled: Boolean = true) { Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { - Button( + ExpressiveButton( onClick = onScan, enabled = scanEnabled, modifier = Modifier.weight(1f), - colors = ButtonDefaults.buttonColors(containerColor = PrimaryBlue, contentColor = Color.White), + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary, contentColor = MaterialTheme.colorScheme.onPrimary), ) { Icon(Icons.Default.QrCodeScanner, contentDescription = null, modifier = Modifier.size(18.dp)) Spacer(Modifier.width(6.dp)) Text("Scan to Add") } - Button( + ExpressiveButton( onClick = onMyQr, modifier = Modifier.weight(1f), - colors = ButtonDefaults.buttonColors(containerColor = Color.White, contentColor = PrimaryBlue), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.secondaryContainer, + contentColor = MaterialTheme.colorScheme.onSecondaryContainer, + ), ) { - Icon(Icons.Default.QrCode, contentDescription = null, modifier = Modifier.size(18.dp), tint = PrimaryBlue) + Icon(Icons.Default.QrCode, contentDescription = null, modifier = Modifier.size(18.dp)) Spacer(Modifier.width(6.dp)) Text("My Friend QR") } @@ -848,10 +881,10 @@ private fun FriendQrActions(onScan: () -> Unit, onMyQr: () -> Unit, scanEnabled: } @Composable -private fun ActionButton(text: String, color: Color, onClick: () -> Unit) { - Button( +private fun ActionButton(text: String, color: Color, contentColor: Color, onClick: () -> Unit) { + ExpressiveButton( onClick = onClick, - colors = ButtonDefaults.buttonColors(containerColor = color, contentColor = Color.White), + colors = ButtonDefaults.buttonColors(containerColor = color, contentColor = contentColor), contentPadding = PaddingValues(horizontal = 10.dp, vertical = 4.dp), ) { Text(text, fontSize = 11.sp) @@ -860,25 +893,25 @@ private fun ActionButton(text: String, color: Color, onClick: () -> Unit) { @Composable private fun FriendCodeCard(friendCode: FriendCode) { - Card(Modifier.fillMaxWidth(), shape = RoundedCornerShape(8.dp), colors = CardDefaults.cardColors(containerColor = CardWhite)) { + ExpressiveGlassCard(Modifier.fillMaxWidth()) { Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { - Text("My Friend Code", fontSize = 18.sp, fontWeight = FontWeight.Bold, color = DarkText) + Text("My Friend Code", fontSize = 18.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween) { - Text(friendCode.code, fontSize = 22.sp, fontWeight = FontWeight.ExtraBold, color = PrimaryBlue) + Text(friendCode.code, fontSize = 22.sp, fontWeight = FontWeight.ExtraBold, color = MaterialTheme.colorScheme.primary) CopyButton(friendCode.code) } - if (friendCode.name.isNotBlank()) Text(friendCode.name, fontSize = 13.sp, color = MutedText) + if (friendCode.name.isNotBlank()) Text(friendCode.name, fontSize = 13.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } } } @Composable private fun CompactFriendCodeCard(friendCode: FriendCode) { - Card(Modifier.fillMaxWidth(), shape = RoundedCornerShape(8.dp), colors = CardDefaults.cardColors(containerColor = CardWhite)) { + ExpressiveGlassCard(Modifier.fillMaxWidth()) { Row(Modifier.fillMaxWidth().padding(horizontal = 14.dp, vertical = 8.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween) { Column { - Text("My Friend Code", fontSize = 11.sp, color = MutedText) - Text(friendCode.code, fontSize = 16.sp, fontWeight = FontWeight.Bold, color = PrimaryBlue) + Text("My Friend Code", fontSize = 11.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) + Text(friendCode.code, fontSize = 16.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.primary) } CopyButton(friendCode.code) } @@ -887,10 +920,17 @@ private fun CompactFriendCodeCard(friendCode: FriendCode) { @Composable private fun SearchFriendCard(code: String, onCode: (String) -> Unit, onSearch: () -> Unit) { - Card(Modifier.fillMaxWidth(), shape = RoundedCornerShape(8.dp), colors = CardDefaults.cardColors(containerColor = CardWhite)) { + ExpressiveGlassCard(Modifier.fillMaxWidth()) { Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { - OutlinedTextField(value = code, onValueChange = onCode, label = { Text("Friend Code") }, modifier = Modifier.fillMaxWidth(), singleLine = true) - Button(onClick = onSearch, enabled = code.isNotBlank()) { Text("Search") } + ExpressiveTextField( + value = code, + onValueChange = onCode, + label = { Text("Friend Code") }, + modifier = Modifier.fillMaxWidth(), + singleLine = true, + leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) }, + ) + ExpressiveButton(onClick = onSearch, enabled = code.isNotBlank()) { Text("Search") } } } } @@ -898,7 +938,7 @@ private fun SearchFriendCard(code: String, onCode: (String) -> Unit, onSearch: ( @Composable private fun FriendQrScannerDialog(onDismiss: () -> Unit, onScanned: (String) -> Unit, onFailure: (String) -> Unit) { Dialog(onDismissRequest = onDismiss) { - Card(Modifier.fillMaxWidth().height(460.dp), shape = RoundedCornerShape(16.dp), colors = CardDefaults.cardColors(containerColor = CardWhite)) { + ExpressiveGlassCard(Modifier.fillMaxWidth().height(460.dp), cornerRadius = 32.dp) { Box(Modifier.fillMaxSize()) { ScannerWithPermissions( modifier = Modifier.fillMaxSize(), @@ -910,8 +950,8 @@ private fun FriendQrScannerDialog(onDismiss: () -> Unit, onScanned: (String) -> cameraPosition = CameraPosition.BACK, enableTorch = false, ) - TextButton(onClick = onDismiss, modifier = Modifier.align(Alignment.TopEnd).padding(8.dp)) { - Text("Close", color = Color.White) + ExpressiveTextButton(onClick = onDismiss, modifier = Modifier.align(Alignment.TopEnd).padding(8.dp)) { + Text("Close", color = MaterialTheme.colorScheme.onPrimary) } } } @@ -945,12 +985,12 @@ private fun MyFriendQrDialog(friendCode: FriendCode, onDismiss: () -> Unit) { } } } - AlertDialog( + ExpressiveAlertDialog( onDismissRequest = onDismiss, title = { Text("My Friend QR") }, text = { Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(12.dp)) { - Card(shape = RoundedCornerShape(12.dp), colors = CardDefaults.cardColors(containerColor = Color.White)) { + ExpressiveGlassCard(cornerRadius = 28.dp, containerColor = Color.White) { Box(Modifier.padding(14.dp), contentAlignment = Alignment.Center) { Image( painter = painter, @@ -959,11 +999,11 @@ private fun MyFriendQrDialog(friendCode: FriendCode, onDismiss: () -> Unit) { ) } } - Text(friendCode.code, fontSize = 20.sp, fontWeight = FontWeight.ExtraBold, color = PrimaryBlue) - friendCode.name.takeIf { it.isNotBlank() }?.let { Text(it, fontSize = 13.sp, color = MutedText) } + Text(friendCode.code, fontSize = 20.sp, fontWeight = FontWeight.ExtraBold, color = MaterialTheme.colorScheme.primary) + friendCode.name.takeIf { it.isNotBlank() }?.let { Text(it, fontSize = 13.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } } }, - confirmButton = { TextButton(onClick = onDismiss) { Text("Done", color = PrimaryBlue) } }, + confirmButton = { ExpressiveTextButton(onClick = onDismiss) { Text("Done", color = MaterialTheme.colorScheme.primary) } }, ) } @@ -976,90 +1016,86 @@ private fun CircleSearchDialog( onDismiss: () -> Unit, onFetchToken: () -> Unit, ) { - AlertDialog( + ExpressiveAlertDialog( onDismissRequest = onDismiss, title = { Text("Search Circles") }, text = { Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { - OutlinedTextField( + ExpressiveTextField( value = code, onValueChange = onCode, modifier = Modifier.fillMaxWidth(), label = { Text("Circle code") }, singleLine = true, - ) - page?.message?.takeIf { it.isNotBlank() }?.let { Text(it, color = MutedText, fontSize = 13.sp) } + ) + page?.message?.takeIf { it.isNotBlank() }?.let { Text(it, color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = 13.sp) } } }, confirmButton = { - TextButton( + ExpressiveTextButton( onClick = onSearch, enabled = code.isNotBlank() && page?.token?.isNotBlank() == true, - ) { Text("Search", color = PrimaryBlue) } + ) { Text("Search", color = MaterialTheme.colorScheme.primary) } }, dismissButton = { - TextButton(onClick = onDismiss) { Text("Cancel") } + ExpressiveTextButton(onClick = onDismiss) { Text("Cancel") } }, ) } @Composable private fun CircleHomeCard(home: CircleHome, onNavigate: (FriendsSection) -> Unit = {}) { - Card(Modifier.fillMaxWidth(), shape = RoundedCornerShape(8.dp), colors = CardDefaults.cardColors(containerColor = CardWhite)) { + ExpressiveGlassCard(Modifier.fillMaxWidth(), cornerRadius = 28.dp) { Box { MaimaiRemoteImage(home.backgroundImageUrl, modifier = Modifier.fillMaxWidth().height(170.dp), contentDescription = null, contentScale = ContentScale.Crop) - Box(Modifier.matchParentSize().background(Color.White.copy(alpha = 0.74f))) + Box(Modifier.matchParentSize().background(MaterialTheme.colorScheme.surface.copy(alpha = 0.74f))) Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp)) { - MaimaiRemoteImage(home.characterImageUrl ?: home.backgroundImageUrl, modifier = Modifier.size(64.dp).clip(RoundedCornerShape(8.dp)), contentDescription = home.name, contentScale = ContentScale.Crop) + MaimaiRemoteImage(home.characterImageUrl ?: home.backgroundImageUrl, modifier = Modifier.size(64.dp).clip(MaterialTheme.shapes.large), contentDescription = home.name, contentScale = ContentScale.Crop) Column(Modifier.weight(1f)) { - Text(home.name.ifBlank { "Circle" }, fontSize = 20.sp, fontWeight = FontWeight.Bold, color = DarkText) - if (home.ownerName.isNotBlank()) Text(home.ownerName, color = MutedText, fontSize = 12.sp) - if (home.comment.isNotBlank()) Text(home.comment, color = MutedText, fontSize = 12.sp) + Text(home.name.ifBlank { "Circle" }, fontSize = 20.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) + if (home.ownerName.isNotBlank()) Text(home.ownerName, color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = 12.sp) + if (home.comment.isNotBlank()) Text(home.comment, color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = 12.sp) } } if (home.code.isNotBlank()) { Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween) { - Text("Circle Code: ${home.code}", color = PrimaryBlue, fontWeight = FontWeight.SemiBold) + Text("Circle Code: ${home.code}", color = MaterialTheme.colorScheme.primary, fontWeight = FontWeight.SemiBold) CopyButton(home.code) } } if (home.tags.isNotEmpty()) { - Text(home.tags.joinToString(" \u00B7 "), color = MutedText, fontSize = 11.sp) + Text(home.tags.joinToString(" \u00B7 "), color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = 11.sp) } - Text("${home.month}: ${home.totalPoints} PT", color = PrimaryBlue, fontWeight = FontWeight.SemiBold) - Text("Rank ${home.rank} \u00B7 ${home.daysUntilReset} days until reset", color = MutedText, fontSize = 12.sp) - if (home.updatedAt.isNotBlank()) Text("Updated: ${home.updatedAt}", color = MutedText, fontSize = 12.sp) - if (home.nextRewardPoints > 0) Text("Next reward: ${home.nextRewardPoints} PT", color = DarkText) - if (home.festaMessage.isNotBlank()) Text(home.festaMessage, color = MutedText, fontSize = 12.sp) + Text("${home.month}: ${home.totalPoints} PT", color = MaterialTheme.colorScheme.primary, fontWeight = FontWeight.SemiBold) + Text("Rank ${home.rank} \u00B7 ${home.daysUntilReset} days until reset", color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = 12.sp) + if (home.updatedAt.isNotBlank()) Text("Updated: ${home.updatedAt}", color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = 12.sp) + if (home.nextRewardPoints > 0) Text("Next reward: ${home.nextRewardPoints} PT", color = MaterialTheme.colorScheme.onSurface) + if (home.festaMessage.isNotBlank()) Text(home.festaMessage, color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = 12.sp) Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { - TextButton(onClick = { onNavigate(FriendsSection.CircleRanking) }) { Text("Ranking") } - TextButton(onClick = { onNavigate(FriendsSection.CircleAccept) }) { Text("Invites") } - if (home.festaMessage.isNotBlank()) TextButton(onClick = { onNavigate(FriendsSection.Festa) }) { Text("FESTA") } + ExpressiveTextButton(onClick = { onNavigate(FriendsSection.CircleRanking) }) { Text("Ranking") } + ExpressiveTextButton(onClick = { onNavigate(FriendsSection.CircleAccept) }) { Text("Invites") } + if (home.festaMessage.isNotBlank()) ExpressiveTextButton(onClick = { onNavigate(FriendsSection.Festa) }) { Text("FESTA") } } } } } } -@Composable private fun RewardRow(reward: CircleReward) = InfoCard("${reward.points} PT \u2014 ${reward.reward}", DarkText) +@Composable private fun RewardRow(reward: CircleReward) = InfoCard("${reward.points} PT \u2014 ${reward.reward}", MaterialTheme.colorScheme.onSurface) -@Composable private fun CircleRankingRow(entry: CircleRankingEntry) = InfoCard("#${entry.rank} ${entry.name} \u2014 ${entry.points} PT", DarkText) +@Composable private fun CircleRankingRow(entry: CircleRankingEntry) = InfoCard("#${entry.rank} ${entry.name} \u2014 ${entry.points} PT", MaterialTheme.colorScheme.onSurface) -@Composable private fun CircleMemberRow(member: CircleMember) = InfoCard("${member.name} \u00B7 Rating ${member.rating} \u00B7 ${member.points} PT${if (member.status.isNotBlank()) " \u00B7 ${member.status}" else ""}", DarkText) +@Composable private fun CircleMemberRow(member: CircleMember) = InfoCard("${member.name} \u00B7 Rating ${member.rating} \u00B7 ${member.points} PT${if (member.status.isNotBlank()) " \u00B7 ${member.status}" else ""}", MaterialTheme.colorScheme.onSurface) @Composable private fun FestaNav(selected: FestaSection, onSelect: (FestaSection) -> Unit) { Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { FestaSection.entries.forEach { section -> - FilterChip( + ExpressiveFilterChip( selected = selected == section, onClick = { onSelect(section) }, label = { Text(section.label) }, - colors = FilterChipDefaults.filterChipColors( - containerColor = Color.White.copy(alpha = 0.5f), - selectedContainerColor = Color.White, - ), ) } } @@ -1067,49 +1103,49 @@ private fun FestaNav(selected: FestaSection, onSelect: (FestaSection) -> Unit) { @Composable private fun FriendVsCard(vs: FriendVsPage) { - Card(Modifier.fillMaxWidth(), shape = RoundedCornerShape(8.dp), colors = CardDefaults.cardColors(containerColor = CardWhite)) { + ExpressiveGlassCard(Modifier.fillMaxWidth(), cornerRadius = 28.dp) { Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { - Text("Friend VS", fontSize = 18.sp, fontWeight = FontWeight.Bold, color = DarkText) + Text("Friend VS", fontSize = 18.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) if (vs.playerName.isNotBlank() || vs.friendName.isNotBlank()) { Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly, verticalAlignment = Alignment.CenterVertically) { Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.weight(1f)) { - MaimaiRemoteImage(vs.playerIconUrl, modifier = Modifier.size(44.dp).clip(RoundedCornerShape(6.dp)), contentDescription = vs.playerName, contentScale = ContentScale.Crop) + MaimaiRemoteImage(vs.playerIconUrl, modifier = Modifier.size(44.dp).clip(MaterialTheme.shapes.large), contentDescription = vs.playerName, contentScale = ContentScale.Crop) Spacer(Modifier.height(4.dp)) - Text(vs.playerName.ifBlank { "You" }, fontSize = 13.sp, fontWeight = FontWeight.Bold, color = DarkText, maxLines = 1) - if (vs.playerRating > 0) Text("${vs.playerRating}", fontSize = 15.sp, fontWeight = FontWeight.ExtraBold, color = PrimaryBlue) + Text(vs.playerName.ifBlank { "You" }, fontSize = 13.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface, maxLines = 1) + if (vs.playerRating > 0) Text("${vs.playerRating}", fontSize = 15.sp, fontWeight = FontWeight.ExtraBold, color = MaterialTheme.colorScheme.primary) } - Text("VS", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = MutedText) + Text("VS", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurfaceVariant) Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.weight(1f)) { - MaimaiRemoteImage(vs.friendIconUrl, modifier = Modifier.size(44.dp).clip(RoundedCornerShape(6.dp)), contentDescription = vs.friendName, contentScale = ContentScale.Crop) + MaimaiRemoteImage(vs.friendIconUrl, modifier = Modifier.size(44.dp).clip(MaterialTheme.shapes.large), contentDescription = vs.friendName, contentScale = ContentScale.Crop) Spacer(Modifier.height(4.dp)) - Text(vs.friendName.ifBlank { "Friend" }, fontSize = 13.sp, fontWeight = FontWeight.Bold, color = DarkText, maxLines = 1) - if (vs.friendRating > 0) Text("${vs.friendRating}", fontSize = 15.sp, fontWeight = FontWeight.ExtraBold, color = Color(0xFFFB4B78)) + Text(vs.friendName.ifBlank { "Friend" }, fontSize = 13.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface, maxLines = 1) + if (vs.friendRating > 0) Text("${vs.friendRating}", fontSize = 15.sp, fontWeight = FontWeight.ExtraBold, color = MaterialTheme.colorScheme.secondary) } } } - if (vs.message.isNotBlank()) Text(vs.message, color = MutedText, fontSize = 12.sp) - if (vs.genres.isNotEmpty()) Text("Genres: ${vs.genres.joinToString()}", color = MutedText, fontSize = 11.sp) - if (vs.difficulties.isNotEmpty()) Text("Difficulties: ${vs.difficulties.joinToString()}", color = MutedText, fontSize = 11.sp) - if (vs.scoreTypes.isNotEmpty()) Text("Score types: ${vs.scoreTypes.joinToString()}", color = MutedText, fontSize = 11.sp) + if (vs.message.isNotBlank()) Text(vs.message, color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = 12.sp) + if (vs.genres.isNotEmpty()) Text("Genres: ${vs.genres.joinToString()}", color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = 11.sp) + if (vs.difficulties.isNotEmpty()) Text("Difficulties: ${vs.difficulties.joinToString()}", color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = 11.sp) + if (vs.scoreTypes.isNotEmpty()) Text("Score types: ${vs.scoreTypes.joinToString()}", color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = 11.sp) } } } @Composable private fun FestaHistoryRow(history: CircleFestaHistory) { - Card(Modifier.fillMaxWidth(), shape = RoundedCornerShape(8.dp), colors = CardDefaults.cardColors(containerColor = CardWhite)) { + ExpressiveGlassCard(Modifier.fillMaxWidth(), cornerRadius = 24.dp) { Row(Modifier.padding(12.dp), horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.CenterVertically) { - MaimaiRemoteImage(history.imageUrl, modifier = Modifier.size(64.dp).clip(RoundedCornerShape(8.dp)), contentDescription = history.season, contentScale = ContentScale.Crop) + MaimaiRemoteImage(history.imageUrl, modifier = Modifier.size(64.dp).clip(MaterialTheme.shapes.large), contentDescription = history.season, contentScale = ContentScale.Crop) Column(Modifier.weight(1f)) { - Text(history.season.ifBlank { "Past FESTA" }, fontSize = 15.sp, fontWeight = FontWeight.Bold, color = DarkText) - if (history.period.isNotBlank()) Text(history.period, fontSize = 12.sp, color = MutedText) - if (history.team.isNotBlank()) Text(history.team, fontSize = 12.sp, fontWeight = FontWeight.SemiBold, color = Color(0xFFFB4B78)) + Text(history.season.ifBlank { "Past FESTA" }, fontSize = 15.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) + if (history.period.isNotBlank()) Text(history.period, fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) + if (history.team.isNotBlank()) Text(history.team, fontSize = 12.sp, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.secondary) } } } } -@Composable private fun BackButton(text: String, onClick: () -> Unit) = TextButton(onClick = onClick) { +@Composable private fun BackButton(text: String, onClick: () -> Unit) = ExpressiveTextButton(onClick = onClick) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) Text(text) } diff --git a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/HomeTab.kt b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/HomeTab.kt index d02128d..b604c33 100644 --- a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/HomeTab.kt +++ b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/HomeTab.kt @@ -3,13 +3,11 @@ package com.derakkuma.ui.tabs import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Visibility import androidx.compose.material.icons.filled.VisibilityOff import androidx.compose.material3.* -import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -25,6 +23,10 @@ import com.derakkuma.data.MaintenanceException import com.derakkuma.data.RatingData import com.derakkuma.ui.components.BigStat import com.derakkuma.ui.components.ErrorState +import com.derakkuma.ui.components.ExpressiveGlassCard +import com.derakkuma.ui.components.ExpressivePullToRefreshBox +import com.derakkuma.ui.components.ExpressiveLinearProgressIndicator +import com.derakkuma.ui.components.ExpressiveTextButton import com.derakkuma.ui.components.LoadingState import com.derakkuma.ui.components.MaimaiProfile import com.derakkuma.ui.components.PlayHeatmap @@ -134,10 +136,10 @@ fun HomeTab(client: MaimaiClient, cookie: String, onSessionExpired: (() -> Unit) } } - PullToRefreshBox( + ExpressivePullToRefreshBox( isRefreshing = isRefreshing, onRefresh = { - if (maintenanceMode) return@PullToRefreshBox + if (maintenanceMode) return@ExpressivePullToRefreshBox isRefreshing = true refreshKey += 1 }, @@ -147,9 +149,9 @@ fun HomeTab(client: MaimaiClient, cookie: String, onSessionExpired: (() -> Unit) ) { // Thin loading bar at top when refreshing if (loading && data != null) { - LinearProgressIndicator( + ExpressiveLinearProgressIndicator( modifier = Modifier.fillMaxWidth().height(2.dp), - color = PrimaryBlue, + color = MaterialTheme.colorScheme.primary, trackColor = Color.Transparent, ) } @@ -191,24 +193,23 @@ fun HomeTab(client: MaimaiClient, cookie: String, onSessionExpired: (() -> Unit) // Missions if (pd.missions.tasks.isNotEmpty() || pd.missions.deadline.isNotBlank()) { Spacer(Modifier.height(8.dp)) - Card( + ExpressiveGlassCard( modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), + cornerRadius = 28.dp, ) { Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { - Text("Missions", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = DarkText) + Text("Missions", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) if (pd.missions.deadline.isNotBlank()) { - Text("Until ${pd.missions.deadline}", fontSize = 12.sp, color = MutedText) + Text("Until ${pd.missions.deadline}", fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } - Text("Progress: ${pd.missions.progress}", fontSize = 13.sp, fontWeight = FontWeight.SemiBold, color = PrimaryBlue) + Text("Progress: ${pd.missions.progress}", fontSize = 13.sp, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.primary) pd.missions.tasks.forEach { task -> Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, ) { - Text(task.description, fontSize = 13.sp, color = DarkText, modifier = Modifier.weight(1f)) - Text("+${task.reward}", fontSize = 13.sp, fontWeight = FontWeight.Bold, color = AccentPink) + Text(task.description, fontSize = 13.sp, color = MaterialTheme.colorScheme.onSurface, modifier = Modifier.weight(1f)) + Text("+${task.reward}", fontSize = 13.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.secondary) } } } @@ -236,16 +237,15 @@ fun HomeTab(client: MaimaiClient, cookie: String, onSessionExpired: (() -> Unit) @Composable private fun LastPlayedCard(play: RecentPlay, onClick: (() -> Unit)? = null) { - Card( + ExpressiveGlassCard( modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 8.dp) .clickable(enabled = onClick != null) { onClick?.invoke() }, - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), + cornerRadius = 24.dp, ) { Column(modifier = Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) { - Text("Last played", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = DarkText) - if (onClick != null) Text("Tap for details", fontSize = 11.sp, color = PrimaryBlue, fontWeight = FontWeight.SemiBold) + Text("Last played", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) + if (onClick != null) Text("Tap for details", fontSize = 11.sp, color = MaterialTheme.colorScheme.primary, fontWeight = FontWeight.SemiBold) } SongRow( name = play.name, @@ -269,17 +269,17 @@ private fun LastPlayedCard(play: RecentPlay, onClick: (() -> Unit)? = null) { @Composable private fun StatusChip(label: String) { - Card( - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = PrimaryBlue.copy(alpha = 0.1f)), + ExpressiveGlassCard( + cornerRadius = 18.dp, + containerColor = MaterialTheme.colorScheme.primaryContainer, ) { Row( modifier = Modifier.padding(horizontal = 16.dp, vertical = 10.dp), horizontalArrangement = Arrangement.spacedBy(6.dp), verticalAlignment = Alignment.CenterVertically, ) { - Text(label, fontSize = 13.sp, fontWeight = FontWeight.SemiBold, color = PrimaryBlue) - Text("!", fontSize = 13.sp, fontWeight = FontWeight.Bold, color = AccentPink) + Text(label, fontSize = 13.sp, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.onPrimaryContainer) + Text("!", fontSize = 13.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.secondary) } } } @@ -307,28 +307,28 @@ private fun ProfileCard( ratingTrailing = { Spacer(Modifier.weight(1f)) if (friendCode != null) { - TextButton(onClick = onToggleFriendCode) { + ExpressiveTextButton(onClick = onToggleFriendCode) { Icon( imageVector = if (showFriendCode) Icons.Default.Visibility else Icons.Default.VisibilityOff, contentDescription = if (showFriendCode) "Hide friend code" else "Show friend code", - tint = PrimaryBlue, + tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(20.dp), ) Spacer(Modifier.width(4.dp)) Text( text = "Friend Code", fontSize = 12.sp, - color = PrimaryBlue, + color = MaterialTheme.colorScheme.primary, ) } } }, details = { if (friendCode != null && showFriendCode) { - Card( + ExpressiveGlassCard( modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(6.dp), - colors = CardDefaults.cardColors(containerColor = Color(0xFFF3F4F6)), + cornerRadius = 16.dp, + containerColor = Color.White, ) { Row( modifier = Modifier.fillMaxWidth().padding(12.dp), @@ -339,7 +339,7 @@ private fun ProfileCard( text = friendCode.code, fontSize = 20.sp, fontWeight = FontWeight.Bold, - color = DarkText, + color = MaterialTheme.colorScheme.onSurface, ) CopyButton(friendCode.code) } diff --git a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/KamaitachiSettingsTab.kt b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/KamaitachiSettingsTab.kt index e6a33b0..d63954d 100644 --- a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/KamaitachiSettingsTab.kt +++ b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/KamaitachiSettingsTab.kt @@ -1,8 +1,8 @@ package com.derakkuma.ui.tabs +import com.derakkuma.ui.components.ExpressiveLinearProgressIndicator import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Link @@ -16,6 +16,12 @@ import com.derakkuma.data.MaimaiClient import com.derakkuma.data.PlayStore import com.derakkuma.kamaitachi.KamaitachiClient import com.derakkuma.kamaitachi.KamaitachiSyncProgress +import com.derakkuma.ui.components.ExpressiveButton +import com.derakkuma.ui.components.ExpressiveGlassCard +import com.derakkuma.ui.components.ExpressiveSnackbar +import com.derakkuma.ui.components.ExpressiveLoadingIndicator +import com.derakkuma.ui.components.ExpressiveScaffold +import com.derakkuma.ui.components.ExpressiveOutlinedButton import com.derakkuma.ui.theme.* import kotlinx.coroutines.launch @@ -29,14 +35,13 @@ fun KamaitachiSettingsTab( var snackMsg by remember { mutableStateOf(null) } val scope = rememberCoroutineScope() - Scaffold( + ExpressiveScaffold( contentWindowInsets = WindowInsets(0.dp), snackbarHost = { if (snackMsg != null) { - Snackbar( + ExpressiveSnackbar( modifier = Modifier.padding(12.dp), - shape = RoundedCornerShape(8.dp), - action = { TextButton(onClick = { snackMsg = null }) { Text("OK") } }, + action = { com.derakkuma.ui.components.ExpressiveTextButton(onClick = { snackMsg = null }) { Text("OK") } }, ) { Text(snackMsg!!) } } }, @@ -49,36 +54,34 @@ fun KamaitachiSettingsTab( .padding(start = 12.dp, end = 12.dp, bottom = 88.dp), verticalArrangement = Arrangement.spacedBy(8.dp), ) { - Text("Kamaitachi", fontSize = 20.sp, fontWeight = FontWeight.Bold, color = DarkText, modifier = Modifier.padding(bottom = 4.dp)) + Text("Kamaitachi", fontSize = 20.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface, modifier = Modifier.padding(bottom = 4.dp)) - Card( + ExpressiveGlassCard( modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), ) { Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { - Text("Score Sync", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = DarkText) - Text("Sync your recent and archived maimai DX records to Kamaitachi.", fontSize = 12.sp, color = MutedText) + Text("Score Sync", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) + Text("Sync your recent and archived maimai DX records to Kamaitachi.", fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) var syncing by remember { mutableStateOf(false) } var syncProgress by remember { mutableStateOf(null) } if (kamaitachiClient.lastStatus.isNotBlank()) { - Text(kamaitachiClient.lastStatus, fontSize = 12.sp, color = MutedText) + Text(kamaitachiClient.lastStatus, fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } if (syncing && syncProgress != null) { val progress = syncProgress!!.current.toFloat() / syncProgress!!.total.coerceAtLeast(1).toFloat() - LinearProgressIndicator( + ExpressiveLinearProgressIndicator( progress = { progress.coerceIn(0f, 1f) }, modifier = Modifier.fillMaxWidth(), - color = PrimaryBlue, + color = MaterialTheme.colorScheme.primary, ) - Text(syncProgress!!.status, fontSize = 12.sp, color = MutedText) + Text(syncProgress!!.status, fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } if (kamaitachiClient.isLinked) { Row(horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.fillMaxWidth()) { - Button( + ExpressiveButton( onClick = { syncing = true syncProgress = null @@ -99,33 +102,30 @@ fun KamaitachiSettingsTab( } }, modifier = Modifier.weight(1f), - shape = RoundedCornerShape(8.dp), - colors = ButtonDefaults.buttonColors(containerColor = PrimaryBlue), + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary), enabled = !syncing, ) { if (syncing) { - CircularProgressIndicator(modifier = Modifier.size(16.dp), color = CardWhite, strokeWidth = 2.dp) + ExpressiveLoadingIndicator(modifier = Modifier, color = MaterialTheme.colorScheme.onPrimary, size = 16.dp) Spacer(Modifier.width(8.dp)) } Text(if (syncing) "Syncing..." else "Sync Records", fontWeight = FontWeight.SemiBold) } - OutlinedButton( + ExpressiveOutlinedButton( onClick = { kamaitachiClient.disconnect() snackMsg = "Kamaitachi disconnected" }, - shape = RoundedCornerShape(8.dp), enabled = !syncing, ) { Text("Disconnect") } } } else { - Button( + ExpressiveButton( onClick = { kamaitachiClient.beginLogin() }, modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = ButtonDefaults.buttonColors(containerColor = PrimaryBlue), + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary), ) { Icon(Icons.Default.Link, contentDescription = null, modifier = Modifier.size(18.dp)) Spacer(Modifier.width(8.dp)) diff --git a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/MapTab.kt b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/MapTab.kt index 4e040bf..580bda7 100644 --- a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/MapTab.kt +++ b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/MapTab.kt @@ -4,7 +4,6 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.LocationOn import androidx.compose.material3.* @@ -21,10 +20,9 @@ import com.derakkuma.data.UserLocation import com.derakkuma.data.distanceKm import com.derakkuma.location.UserLocationProvider import com.derakkuma.maps.openMapLocation -import com.derakkuma.ui.theme.CardWhite -import com.derakkuma.ui.theme.DarkText -import com.derakkuma.ui.theme.MutedText -import com.derakkuma.ui.theme.PrimaryBlue +import com.derakkuma.ui.components.ExpressiveGlassCard +import com.derakkuma.ui.components.ExpressiveTextButton +import com.derakkuma.ui.components.ExpressiveTopLoadingIndicator import com.swmansion.kmpmaps.core.CameraPosition import com.swmansion.kmpmaps.core.ClusterSettings import com.swmansion.kmpmaps.core.Coordinates @@ -77,22 +75,27 @@ fun MapTab(cacheStore: CacheStore) { Column(Modifier.fillMaxSize().padding(start = 12.dp, end = 12.dp, top = 12.dp)) { Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) { Column { - Text("Cab Map", fontSize = 20.sp, fontWeight = FontWeight.Bold, color = DarkText) - Text(locationStatus(userLocation, locations.size), fontSize = 12.sp, color = MutedText) + Text("Cab Map", fontSize = 20.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) + Text(locationStatus(userLocation, locations.size), fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } - TextButton(onClick = { refresh(force = true) }, enabled = !loading) { + ExpressiveTextButton(onClick = { refresh(force = true) }, enabled = !loading) { Text("Refresh") } } Spacer(Modifier.height(8.dp)) - Card(Modifier.fillMaxWidth().height(260.dp), shape = RoundedCornerShape(12.dp), colors = CardDefaults.cardColors(containerColor = CardWhite)) { + if (loading) { + ExpressiveTopLoadingIndicator(color = MaterialTheme.colorScheme.primary, trackColor = androidx.compose.ui.graphics.Color.Transparent) + Spacer(Modifier.height(6.dp)) + } + + ExpressiveGlassCard(Modifier.fillMaxWidth().height(260.dp), cornerRadius = 28.dp) { if (locations.isEmpty() && loading) { - Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { CircularProgressIndicator(color = PrimaryBlue) } + Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Text("Loading arcade locations…", color = MaterialTheme.colorScheme.onSurfaceVariant) } } else if (locations.isEmpty()) { Box(Modifier.fillMaxSize().padding(16.dp), contentAlignment = Alignment.Center) { - Text(error ?: "No locations loaded yet", color = MutedText) + Text(error ?: "No locations loaded yet", color = MaterialTheme.colorScheme.onSurfaceVariant) } } else { Map( @@ -114,7 +117,7 @@ fun MapTab(cacheStore: CacheStore) { } if (error != null && locations.isNotEmpty()) { - Text("using cached locations: $error", modifier = Modifier.padding(top = 8.dp), fontSize = 12.sp, color = MutedText) + Text("using cached locations: $error", modifier = Modifier.padding(top = 8.dp), fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } Spacer(Modifier.height(8.dp)) @@ -129,19 +132,18 @@ fun MapTab(cacheStore: CacheStore) { @Composable private fun ArcadeLocationRow(location: ArcadeLocation, userLocation: UserLocation?) { - Card( + ExpressiveGlassCard( Modifier.fillMaxWidth().clickable { openMapLocation(location.name, location.latitude, location.longitude, location.mapsUrl) }, - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), + cornerRadius = 24.dp, ) { Row(Modifier.fillMaxWidth().padding(12.dp), horizontalArrangement = Arrangement.spacedBy(10.dp), verticalAlignment = Alignment.Top) { - Icon(Icons.Outlined.LocationOn, contentDescription = null, tint = PrimaryBlue, modifier = Modifier.size(22.dp)) + Icon(Icons.Outlined.LocationOn, contentDescription = null, tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(22.dp)) Column(Modifier.weight(1f)) { - Text(location.name, fontWeight = FontWeight.Bold, color = DarkText, fontSize = 14.sp) - if (location.address.isNotBlank()) Text(location.address, color = MutedText, fontSize = 12.sp, lineHeight = 15.sp) + Text(location.name, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface, fontSize = 14.sp) + if (location.address.isNotBlank()) Text(location.address, color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = 12.sp, lineHeight = 15.sp) } userLocation?.let { - Text(formatDistance(distanceKm(it, location)), color = PrimaryBlue, fontWeight = FontWeight.Bold, fontSize = 12.sp) + Text(formatDistance(distanceKm(it, location)), color = MaterialTheme.colorScheme.primary, fontWeight = FontWeight.Bold, fontSize = 12.sp) } } } diff --git a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/MoreTab.kt b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/MoreTab.kt index e9f6ac0..ba6af5d 100644 --- a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/MoreTab.kt +++ b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/MoreTab.kt @@ -2,13 +2,16 @@ package com.derakkuma.ui.tabs import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.infiniteRepeatable import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.* @@ -17,6 +20,7 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.graphics.vector.ImageVector @@ -32,18 +36,12 @@ import com.derakkuma.ui.icons.MapSearchOutlined import com.derakkuma.ui.icons.PersonPinCircleOutlined import com.derakkuma.ui.icons.Signature import com.derakkuma.ui.icons.SocialLeaderboardOutlined +import com.derakkuma.ui.components.ExpressiveGlassCard +import com.derakkuma.ui.components.ExpressiveTextButton import com.derakkuma.ui.images.BundledIcon import com.derakkuma.ui.images.BundledImage import com.derakkuma.ui.theme.* -import com.derakkuma.ui.theme.DangerRed -private val SettingsCardTint = Color(0xFFBA47DE).asCardTint() -private val CollectionCardTint = Color(0xFF3CB9ED).asCardTint() -private val ShopCardTint = Color(0xFF5FDDB7).asCardTint() -private val PlayDataCardTint = Color(0xFFEFB944).asCardTint() -private val RankingCardTint = Color(0xFFBA6EEE).asCardTint() -private val EventCardTint = Color(0xFF7CAB44).asCardTint() -private val KamaitachiCardTint = Color(0xFFCD226F).asCardTint() @Composable fun MoreMenu( @@ -68,13 +66,25 @@ fun MoreMenu( animationSpec = infiniteRepeatable(animation = tween(durationMillis = 8_000, easing = LinearEasing)), label = "app-icon-hue", ) - val appIconRainbowTint = Color.hsv(appIconHue, 0.55f, 1f).asCardTint(alpha = 0.2f) + val appIconRainbowTint = Color.hsv(appIconHue, 0.55f, 1f).asCardTint(base = MaterialTheme.colorScheme.surface, alpha = 0.2f) + val settingsCardTint = MaterialTheme.colorScheme.primaryContainer + val collectionCardTint = MaterialTheme.colorScheme.tertiaryContainer + val shopCardTint = MaterialTheme.colorScheme.secondaryContainer + val playDataCardTint = Color.White + val rankingCardTint = MaterialTheme.colorScheme.primaryContainer + val eventCardTint = MaterialTheme.colorScheme.tertiaryContainer + val kamaitachiCardTint = MaterialTheme.colorScheme.errorContainer + val favoriteSongsCardTint = ExpressiveBubblegum.copy(alpha = 0.18f).compositeOver(Color.White) + val playerNameCardTint = ExpressivePeach.copy(alpha = 0.22f).compositeOver(Color.White) + val mapCardTint = ExpressiveSky.copy(alpha = 0.18f).compositeOver(Color.White) + val bubbleCardTint = ExpressiveMint.copy(alpha = 0.5f).compositeOver(Color.White) + val appSettingsCardTint = ExpressiveGrape.copy(alpha = 0.16f).compositeOver(Color.White) Column( modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(start = 12.dp, end = 12.dp, top = 12.dp, bottom = 88.dp), verticalArrangement = Arrangement.spacedBy(8.dp), ) { - Text("More", fontSize = 20.sp, fontWeight = FontWeight.Bold, color = DarkText, modifier = Modifier.padding(bottom = 4.dp)) + Text("More", fontSize = 20.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface, modifier = Modifier.padding(bottom = 4.dp)) BentoCard( icon = Icons.Outlined.Settings, @@ -83,7 +93,7 @@ fun MoreMenu( description = "Note speed, timing, sound, display options, friend registration, etc.", onClick = onSettings, disabled = maintenanceMode, - containerColor = SettingsCardTint, + containerColor = settingsCardTint, modifier = Modifier.fillMaxWidth(), ) @@ -95,7 +105,7 @@ fun MoreMenu( description = "", onClick = onCollection, disabled = maintenanceMode, - containerColor = CollectionCardTint, + containerColor = collectionCardTint, modifier = Modifier.weight(1f).fillMaxHeight(), ) BentoCard( @@ -107,7 +117,7 @@ fun MoreMenu( onClick = {}, disabled = true, disabledLabel = "Coming soon", - containerColor = ShopCardTint, + containerColor = shopCardTint, modifier = Modifier.weight(1f).fillMaxHeight(), ) } @@ -119,7 +129,7 @@ fun MoreMenu( title = "Player Data", onClick = onPlayerData, compact = true, - containerColor = PlayDataCardTint, + containerColor = playDataCardTint, modifier = Modifier.weight(1f).fillMaxHeight(), ) BentoCard( @@ -129,6 +139,7 @@ fun MoreMenu( onClick = onFavorites, disabled = maintenanceMode, compact = true, + containerColor = favoriteSongsCardTint, modifier = Modifier.weight(1f).fillMaxHeight(), ) BentoCard( @@ -138,6 +149,7 @@ fun MoreMenu( onClick = onPlayerName, disabled = maintenanceMode, compact = true, + containerColor = playerNameCardTint, modifier = Modifier.weight(1f).fillMaxHeight(), ) } @@ -149,7 +161,7 @@ fun MoreMenu( title = "Leaderboards", onClick = onRanking, disabled = maintenanceMode, - containerColor = RankingCardTint, + containerColor = rankingCardTint, modifier = Modifier.weight(1f).fillMaxHeight(), ) BentoCard( @@ -159,7 +171,7 @@ fun MoreMenu( onClick = {}, disabled = true, disabledLabel = "Coming soon", - containerColor = EventCardTint, + containerColor = eventCardTint, modifier = Modifier.weight(1f).fillMaxHeight(), ) } @@ -171,6 +183,7 @@ fun MoreMenu( title = "Cab Map", description = "", onClick = onMap, + containerColor = mapCardTint, modifier = Modifier.weight(1f).fillMaxHeight(), ) BentoCard( @@ -179,6 +192,7 @@ fun MoreMenu( title = "Bubble", description = "", onClick = onBubble, + containerColor = bubbleCardTint, modifier = Modifier.weight(1f).fillMaxHeight(), ) } @@ -190,6 +204,7 @@ fun MoreMenu( title = "App Settings", description = "Cache, account, about", onClick = onAppSettings, + containerColor = appSettingsCardTint, modifier = Modifier.weight(1f).fillMaxHeight(), ) BentoCard( @@ -199,7 +214,7 @@ fun MoreMenu( description = "Sync records", onClick = onKamaitachi, disabled = maintenanceMode, - containerColor = KamaitachiCardTint, + containerColor = kamaitachiCardTint, modifier = Modifier.weight(1f).fillMaxHeight(), ) } @@ -220,17 +235,17 @@ fun MoreMenu( } Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { - TextButton(onClick = { openOAuthUrl("https://derakkuma.com/terms") }) { + ExpressiveTextButton(onClick = { openOAuthUrl("https://derakkuma.com/terms") }) { Text("Terms of Service", fontSize = 11.sp, color = Color.White) } - TextButton(onClick = { openOAuthUrl("https://derakkuma.com/privacy") }) { + ExpressiveTextButton(onClick = { openOAuthUrl("https://derakkuma.com/privacy") }) { Text("Privacy Policy", fontSize = 11.sp, color = Color.White) } } } } -private fun Color.asCardTint(alpha: Float = 0.14f): Color = copy(alpha = alpha).compositeOver(Color.White) +private fun Color.asCardTint(base: Color, alpha: Float = 0.14f): Color = copy(alpha = alpha).compositeOver(base) @Composable private fun BentoRow(content: @Composable RowScope.() -> Unit) { @@ -254,19 +269,30 @@ private fun BentoCard( descriptionIcon: BundledImage? = null, onClick: () -> Unit, modifier: Modifier = Modifier, - containerColor: Color = CardWhite, + containerColor: Color = MaterialTheme.colorScheme.surfaceContainerHigh, disabled: Boolean = false, disabledLabel: String = "Maintenance", compact: Boolean = false, ) { val hasDetails = description.isNotBlank() || disabled + val interactionSource = remember { MutableInteractionSource() } + val pressed by interactionSource.collectIsPressedAsState() + val scale by animateFloatAsState( + targetValue = if (pressed && !disabled) 0.97f else 1f, + animationSpec = spring(dampingRatio = 0.62f, stiffness = 460f), + label = "more-card-press-scale", + ) - Card( - modifier = modifier, - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = containerColor), + ExpressiveGlassCard( + modifier = modifier.scale(scale), + cornerRadius = if (compact) 22.dp else 28.dp, + containerColor = containerColor, ) { - Box(modifier = Modifier.fillMaxSize().clickable(enabled = !disabled, onClick = onClick)) { + Box( + modifier = Modifier + .fillMaxSize() + .clickable(enabled = !disabled, interactionSource = interactionSource, indication = null, onClick = onClick), + ) { backgroundStartImage?.let { image -> BundledIcon( image = image, @@ -297,10 +323,10 @@ private fun BentoCard( modifier = Modifier.size(26.dp).alpha(if (disabled) 0.45f else 1f), ) } else { - Icon(icon, contentDescription = null, modifier = Modifier.size(26.dp), tint = if (disabled) MutedText.copy(alpha = 0.4f) else PrimaryBlue) + Icon(icon, contentDescription = null, modifier = Modifier.size(26.dp), tint = if (disabled) MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.4f) else MaterialTheme.colorScheme.primary) } } - Text(title, fontSize = if (compact) 13.sp else 15.sp, fontWeight = FontWeight.SemiBold, color = if (disabled) MutedText else DarkText) + Text(title, fontSize = if (compact) 13.sp else 15.sp, fontWeight = FontWeight.SemiBold, color = if (disabled) MaterialTheme.colorScheme.onSurfaceVariant else MaterialTheme.colorScheme.onSurface) } if (hasDetails) { Column( @@ -323,14 +349,14 @@ private fun BentoCard( Text( description, fontSize = 11.sp, - color = MutedText, + color = MaterialTheme.colorScheme.onSurfaceVariant, lineHeight = 13.sp, textAlign = if (centerText) TextAlign.Center else TextAlign.Start, ) } } if (disabled) { - Text(disabledLabel, fontSize = 10.sp, fontWeight = FontWeight.Bold, color = DangerRed.copy(alpha = 0.7f)) + Text(disabledLabel, fontSize = 10.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.error.copy(alpha = 0.7f)) } } } diff --git a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/MusicDetailTab.kt b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/MusicDetailTab.kt index 95a9bf4..522e770 100644 --- a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/MusicDetailTab.kt +++ b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/MusicDetailTab.kt @@ -2,9 +2,11 @@ package com.derakkuma.ui.tabs import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.MaterialShapes import androidx.compose.material3.* +import androidx.compose.material3.toShape import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -22,11 +24,15 @@ import com.derakkuma.data.* import com.derakkuma.data.CacheStore import com.derakkuma.ui.components.EmptyState import com.derakkuma.ui.components.ErrorState +import com.derakkuma.ui.components.ExpressiveGlassCard +import com.derakkuma.ui.components.ExpressiveHorizontalDivider +import com.derakkuma.ui.components.ExpressiveBadgeSurface import com.derakkuma.ui.components.LoadingState import com.derakkuma.ui.images.* import com.derakkuma.ui.theme.* import com.derakkuma.util.formatScore +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun MusicDetailTab(client: MaimaiClient, cookie: String, idx: String, maintenanceMode: Boolean = false, cacheStore: CacheStore = CacheStore(), atProtoClient: AtProtoClient? = null) { var detail by remember { mutableStateOf(null) } @@ -77,11 +83,7 @@ fun MusicDetailTab(client: MaimaiClient, cookie: String, idx: String, maintenanc val d = detail!! // Song header - Card( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), - ) { + ExpressiveGlassCard(modifier = Modifier.fillMaxWidth(), cornerRadius = 26.dp) { Row( modifier = Modifier.padding(12.dp), verticalAlignment = Alignment.CenterVertically, @@ -90,15 +92,15 @@ fun MusicDetailTab(client: MaimaiClient, cookie: String, idx: String, maintenanc if (d.albumArtUrl != null) { MaimaiRemoteImage( url = d.albumArtUrl, - modifier = Modifier.size(60.dp).clip(RoundedCornerShape(4.dp)), + modifier = Modifier.size(60.dp).clip(MaterialShapes.Square.toShape()), contentDescription = d.name, contentScale = ContentScale.Crop, ) } Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(3.dp)) { - Text(d.name, fontSize = 16.sp, fontWeight = FontWeight.Bold, color = DarkText, maxLines = 2) + Text(d.name, fontSize = 16.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface, maxLines = 2) if (d.artist.isNotBlank()) { - Text(d.artist, fontSize = 12.sp, color = MutedText) + Text(d.artist, fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } } } @@ -108,30 +110,22 @@ fun MusicDetailTab(client: MaimaiClient, cookie: String, idx: String, maintenanc // All difficulty scores if (d.scores.isNotEmpty()) { - Card( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), - ) { + ExpressiveGlassCard(modifier = Modifier.fillMaxWidth(), cornerRadius = 26.dp) { Column(modifier = Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { - Text("All Difficulties", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = DarkText) + Text("All Difficulties", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) d.scores.forEach { score -> DifficultyScoreRow(score) if (score != d.scores.last()) { - HorizontalDivider(color = MutedText.copy(alpha = 0.15f)) + ExpressiveHorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.7f)) } } } } } else { - Card( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), - ) { + ExpressiveGlassCard(modifier = Modifier.fillMaxWidth(), cornerRadius = 24.dp) { Box(Modifier.fillMaxWidth().padding(24.dp), contentAlignment = Alignment.Center) { - Text("No scores recorded", fontSize = 14.sp, color = MutedText) + Text("No scores recorded", fontSize = 14.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } } } @@ -147,30 +141,26 @@ private fun AtProtoLeaderboardsCard( leaderboards: Map>, error: String?, ) { - Card( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), - ) { + ExpressiveGlassCard(modifier = Modifier.fillMaxWidth(), cornerRadius = 26.dp) { Column(modifier = Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { - Text("AT Protocol Leaderboards", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = DarkText) + Text("AT Protocol Leaderboards", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) if (error != null) { Text(error, fontSize = 12.sp, color = MaterialTheme.colorScheme.error) return@Column } if (leaderboards.isEmpty()) { - Text("No published scores yet.", fontSize = 12.sp, color = MutedText) + Text("No published scores yet.", fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } else { leaderboards.forEach { (difficulty, entries) -> Text(difficulty.toAtProtoDifficultyName(), fontSize = 13.sp, fontWeight = FontWeight.SemiBold, color = difficultyColor(difficulty)) if (entries.isEmpty()) { - Text("No entries", fontSize = 11.sp, color = MutedText) + Text("No entries", fontSize = 11.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } else { entries.take(5).forEachIndexed { index, entry -> Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) { val label = entry.playerName.ifBlank { entry.did.take(12) + "…" } - Text("#${entry.rank.takeIf { it > 0 } ?: index + 1} $label", fontSize = 12.sp, color = DarkText, modifier = Modifier.weight(1f)) - Text("${formatScore(entry.achievement.toDoubleOrNull() ?: 0.0)}%", fontSize = 12.sp, fontWeight = FontWeight.Bold, color = AccentPink) + Text("#${entry.rank.takeIf { it > 0 } ?: index + 1} $label", fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurface, modifier = Modifier.weight(1f)) + Text("${formatScore(entry.achievement.toDoubleOrNull() ?: 0.0)}%", fontSize = 12.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.secondary) } } } @@ -187,9 +177,9 @@ private fun DifficultyScoreRow(score: DifficultyScore) { verticalAlignment = Alignment.CenterVertically, ) { // Difficulty badge - Surface( - shape = RoundedCornerShape(4.dp), - color = difficultyColor(score.difficulty).copy(alpha = 0.15f), + ExpressiveBadgeSurface( + shape = MaterialTheme.shapes.large, + containerColor = difficultyColor(score.difficulty).copy(alpha = 0.15f), ) { Text( " ${score.difficulty.replaceFirstChar { it.uppercase() }} ${score.level} ", @@ -214,7 +204,7 @@ private fun DifficultyScoreRow(score: DifficultyScore) { "${formatScore(score.achievement)}%", fontSize = 13.sp, fontWeight = FontWeight.Bold, - color = AccentPink, + color = MaterialTheme.colorScheme.secondary, ) } if (score.scoreRank.isNotBlank()) { @@ -237,13 +227,13 @@ private fun DifficultyScoreRow(score: DifficultyScore) { // DX score + play info Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) { if (score.dxScore.achieved > 0) { - Text("DX ${score.dxScore.achieved}/${score.dxScore.total}", fontSize = 10.sp, color = MutedText) + Text("DX ${score.dxScore.achieved}/${score.dxScore.total}", fontSize = 10.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } if (score.playCount > 0) { - Text("Plays: ${score.playCount}", fontSize = 10.sp, color = MutedText) + Text("Plays: ${score.playCount}", fontSize = 10.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } if (score.lastPlayed.isNotBlank()) { - Text(score.lastPlayed, fontSize = 10.sp, color = MutedText) + Text(score.lastPlayed, fontSize = 10.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } } } diff --git a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/PlayDetailTab.kt b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/PlayDetailTab.kt index c2c67f5..4777ec8 100644 --- a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/PlayDetailTab.kt +++ b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/PlayDetailTab.kt @@ -2,9 +2,11 @@ package com.derakkuma.ui.tabs import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.MaterialShapes import androidx.compose.material3.* +import androidx.compose.material3.toShape import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -20,6 +22,8 @@ import com.derakkuma.data.* import com.derakkuma.data.CacheStore import com.derakkuma.ui.components.EmptyState import com.derakkuma.ui.components.ErrorState +import com.derakkuma.ui.components.ExpressiveGlassCard +import com.derakkuma.ui.components.ExpressiveHorizontalDivider import com.derakkuma.ui.components.LoadingState import com.derakkuma.ui.images.* import com.derakkuma.ui.theme.* @@ -34,6 +38,7 @@ import network.chaintech.cmpcharts.ui.segmentprogresschart.model.SegmentInfo import network.chaintech.cmpcharts.ui.segmentprogresschart.model.SegmentProgressBarColors import network.chaintech.cmpcharts.ui.segmentprogresschart.model.SegmentProgressBarConfig +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun PlayDetailTab(client: MaimaiClient, cookie: String, idx: String, onMusicDetail: ((idx: String) -> Unit)? = null, maintenanceMode: Boolean = false, cacheStore: CacheStore = CacheStore(), playStore: PlayStore? = null) { var detail by remember { mutableStateOf(null) } @@ -73,11 +78,7 @@ fun PlayDetailTab(client: MaimaiClient, cookie: String, idx: String, onMusicDeta val d = detail!! // Song header - Card( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), - ) { + ExpressiveGlassCard(modifier = Modifier.fillMaxWidth(), cornerRadius = 26.dp) { Row( modifier = Modifier.padding(12.dp), verticalAlignment = Alignment.CenterVertically, @@ -86,19 +87,19 @@ fun PlayDetailTab(client: MaimaiClient, cookie: String, idx: String, onMusicDeta if (d.albumArtUrl != null) { MaimaiRemoteImage( url = d.albumArtUrl, - modifier = Modifier.size(60.dp).clip(RoundedCornerShape(4.dp)), + modifier = Modifier.size(60.dp).clip(MaterialShapes.Square.toShape()), contentDescription = d.name, contentScale = ContentScale.Crop, ) } Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(3.dp)) { - Text(d.name, fontSize = 16.sp, fontWeight = FontWeight.Bold, color = DarkText, maxLines = 2) + Text(d.name, fontSize = 16.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface, maxLines = 2) Row(horizontalArrangement = Arrangement.spacedBy(6.dp), verticalAlignment = Alignment.CenterVertically) { val levelText = songDbLevel?.let { "Lv ${d.level} (${formatScore(it, 1)})" } ?: "Lv ${d.level}" Text(levelText, fontSize = 12.sp, fontWeight = FontWeight.Bold, color = difficultyColor(d.difficulty)) if (d.isDx) BundledIcon(image = BundledImage.MusicDx, modifier = Modifier.height(12.dp), contentDescription = "DX") } - Text(d.datetime, fontSize = 11.sp, color = MutedText) + Text(d.datetime, fontSize = 11.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } } } @@ -106,11 +107,7 @@ fun PlayDetailTab(client: MaimaiClient, cookie: String, idx: String, onMusicDeta Spacer(Modifier.height(8.dp)) // Score header - Card( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), - ) { + ExpressiveGlassCard(modifier = Modifier.fillMaxWidth(), cornerRadius = 28.dp) { Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) { BigStat("${formatScore(d.achievement)}%", "Achievement") @@ -123,9 +120,9 @@ fun PlayDetailTab(client: MaimaiClient, cookie: String, idx: String, onMusicDeta val deltaColor = if (d.ratingDelta > 0) { DiffBasic } else if (d.ratingDelta < 0) { - DangerRed + MaterialTheme.colorScheme.error } else { - MutedText + MaterialTheme.colorScheme.onSurfaceVariant } val sign = if (d.ratingDelta > 0) "+" else "" BigStat("$sign${d.ratingDelta}", "Delta", color = deltaColor) @@ -140,7 +137,7 @@ fun PlayDetailTab(client: MaimaiClient, cookie: String, idx: String, onMusicDeta BigStat("${d.maxSync.achieved}/${d.maxSync.total}", "Max Sync") Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { FastLateStat("Fast", d.fast, DiffBasic) - FastLateStat("Late", d.late, DangerRed) + FastLateStat("Late", d.late, MaterialTheme.colorScheme.error) } } } @@ -153,13 +150,9 @@ fun PlayDetailTab(client: MaimaiClient, cookie: String, idx: String, onMusicDeta Spacer(Modifier.height(8.dp)) // Note breakdown table - Card( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), - ) { + ExpressiveGlassCard(modifier = Modifier.fillMaxWidth(), cornerRadius = 26.dp) { Column(modifier = Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { - Text("Note Breakdown", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = DarkText) + Text("Note Breakdown", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) NoteBreakdownTable(d.notes) } } @@ -171,8 +164,8 @@ fun PlayDetailTab(client: MaimaiClient, cookie: String, idx: String, onMusicDeta // Button( // onClick = { onMusicDetail(d.musicIdx) }, // modifier = Modifier.fillMaxWidth(), - // shape = RoundedCornerShape(8.dp), - // colors = ButtonDefaults.buttonColors(containerColor = PrimaryBlue), + // shape = MaterialTheme.shapes.medium, + // colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary), // ) { // Text("View All Difficulties", fontSize = 14.sp, fontWeight = FontWeight.SemiBold) // } @@ -188,17 +181,17 @@ private fun RankStat(rank: String) { if (rankIcon != null) { BundledIcon(image = rankIcon, modifier = Modifier.height(28.dp), contentDescription = rank) } else { - Text(rank.uppercase(), fontSize = 18.sp, fontWeight = FontWeight.Bold, color = DarkText) + Text(rank.uppercase(), fontSize = 18.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) } - Text("Rank", fontSize = 10.sp, color = MutedText) + Text("Rank", fontSize = 10.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } } @Composable -private fun BigStat(value: String, label: String, color: androidx.compose.ui.graphics.Color = DarkText) { +private fun BigStat(value: String, label: String, color: androidx.compose.ui.graphics.Color = MaterialTheme.colorScheme.onSurface) { Column(horizontalAlignment = Alignment.CenterHorizontally) { Text(value, fontSize = 18.sp, fontWeight = FontWeight.Bold, color = color) - Text(label, fontSize = 10.sp, color = MutedText) + Text(label, fontSize = 10.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } } @@ -206,7 +199,7 @@ private fun BigStat(value: String, label: String, color: androidx.compose.ui.gra private fun FastLateStat(label: String, count: Int, color: androidx.compose.ui.graphics.Color) { Column(horizontalAlignment = Alignment.CenterHorizontally) { Text("$count", fontSize = 18.sp, fontWeight = FontWeight.Bold, color = color) - Text(label, fontSize = 10.sp, color = MutedText) + Text(label, fontSize = 10.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } } @@ -226,26 +219,26 @@ private fun NoteBreakdownTable(notes: NoteBreakdown) { modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp), horizontalArrangement = Arrangement.SpaceBetween, ) { - Text("Type", fontSize = 11.sp, fontWeight = FontWeight.Bold, color = MutedText, modifier = Modifier.weight(1.2f)) + Text("Type", fontSize = 11.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.weight(1.2f)) columns.drop(1).forEach { col -> - Text(col, fontSize = 11.sp, fontWeight = FontWeight.Bold, color = MutedText, modifier = Modifier.weight(1f), textAlign = TextAlign.Center) + Text(col, fontSize = 11.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.weight(1f), textAlign = TextAlign.Center) } } - HorizontalDivider(color = MutedText.copy(alpha = 0.15f)) + ExpressiveHorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.7f)) rows.forEach { (label, judgement) -> Row( modifier = Modifier.fillMaxWidth().padding(vertical = 3.dp), horizontalArrangement = Arrangement.SpaceBetween, ) { - Text(label, fontSize = 12.sp, fontWeight = FontWeight.SemiBold, color = DarkText, modifier = Modifier.weight(1.2f)) + Text(label, fontSize = 12.sp, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.onSurface, modifier = Modifier.weight(1.2f)) val values = listOf(judgement.criticalPerfect, judgement.perfect, judgement.great, judgement.good, judgement.miss) values.forEach { v -> Text( if (v > 0) "$v" else "-", fontSize = 12.sp, - color = if (v > 0) DarkText else MutedText.copy(alpha = 0.4f), + color = if (v > 0) MaterialTheme.colorScheme.onSurface else MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.4f), modifier = Modifier.weight(1f), textAlign = TextAlign.Center, ) @@ -257,13 +250,9 @@ private fun NoteBreakdownTable(notes: NoteBreakdown) { @Composable private fun PlayBreakdownCharts(detail: PlayLogDetail) { val radarData = detail.notes.noteTypeScores() - Card( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), - ) { + ExpressiveGlassCard(modifier = Modifier.fillMaxWidth(), cornerRadius = 28.dp) { Column(modifier = Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { - Text("Performance Shape", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = DarkText) + Text("Performance Shape", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) if (radarData.any { it.second > 0.0 }) { RadarChart( modifier = Modifier.fillMaxWidth().height(230.dp), @@ -272,15 +261,15 @@ private fun PlayBreakdownCharts(detail: PlayLogDetail) { scaleStepsCount = 4, maxLabelWidth = 40.dp, radarChartLineStyle = RadarChartLineStyle( - color = MutedText.copy(alpha = 0.25f), + color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.7f), strokeWidth = 1f, strokeCap = StrokeCap.Butt, ), radarChartDataSet = listOf( RadarChartDataSet( style = RadarChartDataSetStyle( - color = PrimaryBlue, - borderColor = AccentPink, + color = MaterialTheme.colorScheme.primary, + borderColor = MaterialTheme.colorScheme.secondary, colorAlpha = 0.22f, strokeWidth = 2f, strokeCap = StrokeCap.Round, @@ -288,7 +277,7 @@ private fun PlayBreakdownCharts(detail: PlayLogDetail) { data = radarData.map { it.second }, ), ), - labelTextStyle = TextStyle(color = DarkText, fontSize = 11.sp, fontWeight = FontWeight.SemiBold), + labelTextStyle = TextStyle(color = MaterialTheme.colorScheme.onSurface, fontSize = 11.sp, fontWeight = FontWeight.SemiBold), scaleTextStyle = TextStyle(color = androidx.compose.ui.graphics.Color.Transparent, fontSize = 1.sp), scaleUnit = "%", maxScaleValue = 100.0, @@ -296,13 +285,13 @@ private fun PlayBreakdownCharts(detail: PlayLogDetail) { ), ) } else { - Text("No judgement data for this play", fontSize = 12.sp, color = MutedText) + Text("No judgement data for this play", fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } val timingTotal = detail.fast + detail.late if (timingTotal > 0) { - Text("Timing Bias", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = DarkText) - Text("Late ${detail.late} • Fast ${detail.fast}", fontSize = 12.sp, color = MutedText) + Text("Timing Bias", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.onSurface) + Text("Late ${detail.late} • Fast ${detail.fast}", fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) SegmentedProgressBarChart( modifier = Modifier.fillMaxWidth(), segmentProgressBarConfig = SegmentProgressBarConfig( @@ -313,19 +302,19 @@ private fun PlayBreakdownCharts(detail: PlayLogDetail) { progress = (detail.fast.toFloat() / timingTotal.toFloat()).coerceIn(0f, 1f), scaleLabels = listOf("Late", "", "Even", "", "Fast"), segmentsList = listOf( - SegmentInfo(DangerRed, 1f), + SegmentInfo(MaterialTheme.colorScheme.error, 1f), SegmentInfo(DiffAdvanced, 1f), SegmentInfo(DiffBasic.copy(alpha = 0.55f), 1f), SegmentInfo(DiffAdvanced, 1f), - SegmentInfo(DangerRed, 1f), + SegmentInfo(MaterialTheme.colorScheme.error, 1f), ), displayScale = true, scaleFontSize = 9.sp, shouldAnimateProgress = true, segmentProgressBarColors = SegmentProgressBarColors( - pointerColor = DarkText, - scaleTextColor = MutedText, - scaleColor = MutedText.copy(alpha = 0.35f), + pointerColor = MaterialTheme.colorScheme.onSurface, + scaleTextColor = MaterialTheme.colorScheme.onSurfaceVariant, + scaleColor = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.7f), ), ), ) diff --git a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/PlayerDataTab.kt b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/PlayerDataTab.kt index f67657f..ce7437a 100644 --- a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/PlayerDataTab.kt +++ b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/PlayerDataTab.kt @@ -1,12 +1,13 @@ package com.derakkuma.ui.tabs +import com.derakkuma.ui.components.ExpressiveLinearProgressIndicator import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.Canvas import androidx.compose.foundation.Image +import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material3.* @@ -28,6 +29,8 @@ import com.derakkuma.data.* import com.derakkuma.data.CacheStore import com.derakkuma.ui.components.BigStat import com.derakkuma.ui.components.ErrorState +import com.derakkuma.ui.components.ExpressiveGlassCard +import com.derakkuma.ui.components.ExpressiveDot import com.derakkuma.ui.components.LoadingState import com.derakkuma.ui.images.* import com.derakkuma.ui.theme.* @@ -81,11 +84,7 @@ fun PlayerDataTab(client: MaimaiClient, cookie: String, profile: Profile? = null // Mini profile header if (profile != null) { - Card( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), - ) { + ExpressiveGlassCard(modifier = Modifier.fillMaxWidth(), cornerRadius = 24.dp) { Row( modifier = Modifier.padding(12.dp), verticalAlignment = Alignment.CenterVertically, @@ -93,13 +92,13 @@ fun PlayerDataTab(client: MaimaiClient, cookie: String, profile: Profile? = null ) { MaimaiRemoteImage( url = profile.profileImageUrl, - modifier = Modifier.size(44.dp).clip(CircleShape), + modifier = Modifier.size(44.dp).clip(RoundedCornerShape(2.dp)), contentDescription = "Profile icon", contentScale = ContentScale.Crop, ) Column(modifier = Modifier.weight(1f)) { - Text(profile.name, fontSize = 16.sp, fontWeight = FontWeight.Bold, color = DarkText) - Text("Rating: ${profile.rating}", fontSize = 13.sp, color = MutedText) + Text(profile.name, fontSize = 16.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) + Text("Rating: ${profile.rating}", fontSize = 13.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } } } @@ -108,13 +107,9 @@ fun PlayerDataTab(client: MaimaiClient, cookie: String, profile: Profile? = null } // Play count stats - Card( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), - ) { + ExpressiveGlassCard(modifier = Modifier.fillMaxWidth(), cornerRadius = 26.dp) { Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { - Text("Play Statistics", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = DarkText) + Text("Play Statistics", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) { BigStat("${pd.playCount.currentVersion}", "Current Ver") BigStat("${pd.playCount.total}", "Dashboard") @@ -126,11 +121,7 @@ fun PlayerDataTab(client: MaimaiClient, cookie: String, profile: Profile? = null Spacer(Modifier.height(8.dp)) // Maimile - Card( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), - ) { + ExpressiveGlassCard(modifier = Modifier.fillMaxWidth(), cornerRadius = 24.dp) { Row( modifier = Modifier.padding(16.dp).fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, @@ -145,9 +136,9 @@ fun PlayerDataTab(client: MaimaiClient, cookie: String, profile: Profile? = null modifier = Modifier.height(18.dp), contentDescription = "maimile", ) - Text("maimile", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = DarkText) + Text("maimile", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.onSurface) } - Text("${pd.maimile}", fontSize = 20.sp, fontWeight = FontWeight.Bold, color = AccentPink) + Text("${pd.maimile}", fontSize = 20.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.secondary) } } @@ -163,13 +154,9 @@ fun PlayerDataTab(client: MaimaiClient, cookie: String, profile: Profile? = null // Class CP if (pd.classCP.max > 0) { - Card( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), - ) { + ExpressiveGlassCard(modifier = Modifier.fillMaxWidth(), cornerRadius = 26.dp) { Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { - Text("Class CP", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = DarkText) + Text("Class CP", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) { BigStat("${pd.classCP.current}", "Current") BigStat("${pd.classCP.max}", "Max") @@ -181,11 +168,11 @@ fun PlayerDataTab(client: MaimaiClient, cookie: String, profile: Profile? = null animationSpec = tween(durationMillis = 450), label = "class-cp-progress", ) - LinearProgressIndicator( + ExpressiveLinearProgressIndicator( progress = { progress }, - modifier = Modifier.fillMaxWidth().height(8.dp).clip(RoundedCornerShape(4.dp)), - color = PrimaryBlue, - trackColor = MutedText.copy(alpha = 0.15f), + modifier = Modifier.fillMaxWidth().height(10.dp).clip(MaterialTheme.shapes.extraLarge), + color = MaterialTheme.colorScheme.primary, + trackColor = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.7f), ) } } @@ -196,35 +183,10 @@ fun PlayerDataTab(client: MaimaiClient, cookie: String, profile: Profile? = null // Score rank counters if (pd.scoreCounters.isNotEmpty()) { - Card( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), - ) { + ExpressiveGlassCard(modifier = Modifier.fillMaxWidth(), cornerRadius = 26.dp) { Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { - Text("Score Counters", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = DarkText) - pd.scoreCounters.forEach { counter -> - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - ) { - Text(counter.rank.uppercase(), fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = DarkText) - Text("${counter.achieved} / ${counter.total}", fontSize = 14.sp, color = MutedText) - } - if (counter.total > 0) { - val progress by animateFloatAsState( - targetValue = (counter.achieved.toFloat() / counter.total.toFloat()).coerceIn(0f, 1f), - animationSpec = tween(durationMillis = 450), - label = "score-counter-progress", - ) - LinearProgressIndicator( - progress = { progress }, - modifier = Modifier.fillMaxWidth().height(4.dp).clip(RoundedCornerShape(2.dp)), - color = PrimaryBlue, - trackColor = MutedText.copy(alpha = 0.15f), - ) - } - } + Text("Score Counters", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) + ScoreCounterBarChart(pd.scoreCounters, modifier = Modifier.fillMaxWidth().height(230.dp)) } } @@ -233,24 +195,20 @@ fun PlayerDataTab(client: MaimaiClient, cookie: String, profile: Profile? = null // Missions if (pd.missions.tasks.isNotEmpty() || pd.missions.deadline.isNotEmpty()) { - Card( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), - ) { + ExpressiveGlassCard(modifier = Modifier.fillMaxWidth(), cornerRadius = 26.dp) { Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { - Text("Missions", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = DarkText) + Text("Missions", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) if (pd.missions.deadline.isNotBlank()) { - Text("Until ${pd.missions.deadline}", fontSize = 12.sp, color = MutedText) + Text("Until ${pd.missions.deadline}", fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } - Text("Progress: ${pd.missions.progress}", fontSize = 13.sp, fontWeight = FontWeight.SemiBold, color = PrimaryBlue) + Text("Progress: ${pd.missions.progress}", fontSize = 13.sp, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.primary) pd.missions.tasks.forEach { task -> Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, ) { - Text(task.description, fontSize = 13.sp, color = DarkText, modifier = Modifier.weight(1f)) - Text("+${task.reward}", fontSize = 13.sp, fontWeight = FontWeight.Bold, color = AccentPink) + Text(task.description, fontSize = 13.sp, color = MaterialTheme.colorScheme.onSurface, modifier = Modifier.weight(1f)) + Text("+${task.reward}", fontSize = 13.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.secondary) } } } @@ -271,22 +229,73 @@ fun PlayerDataTab(client: MaimaiClient, cookie: String, profile: Profile? = null } } +@Composable +private fun ScoreCounterBarChart(counters: List, modifier: Modifier = Modifier) { + val displayCounters = remember(counters) { counters.filter { it.total > 0 || it.achieved > 0 } } + if (displayCounters.isEmpty()) return + val animatedFractions = displayCounters.mapIndexed { index, counter -> + val target = if (counter.total > 0) counter.achieved.toFloat() / counter.total.toFloat() else 0f + animateFloatAsState( + targetValue = target.coerceIn(0f, 1f), + animationSpec = tween(durationMillis = 520 + index * 55), + label = "score-counter-progress-${counter.rank}", + ).value + } + + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(10.dp), + ) { + displayCounters.forEachIndexed { index, counter -> + val color = scoreCounterColor(index, MaterialTheme.colorScheme.primary) + Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Text(counter.rank.uppercase(), fontSize = 12.sp, fontWeight = FontWeight.Bold, color = color) + Text("${counter.achieved} / ${counter.total}", fontSize = 12.sp, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.onSurface) + } + Box( + modifier = Modifier + .fillMaxWidth() + .height(12.dp) + .clip(MaterialTheme.shapes.extraLarge) + .background(color.copy(alpha = 0.14f)), + ) { + Box( + modifier = Modifier + .fillMaxHeight() + .fillMaxWidth(animatedFractions[index]) + .clip(MaterialTheme.shapes.extraLarge) + .background(color), + ) + } + } + } + } +} + +private fun scoreCounterColor(index: Int, primaryColor: Color): Color { + val colors = listOf(primaryColor, DiffMaster, DiffExpert, DiffAdvanced, DiffBasic) + return colors[index % colors.size] +} + @Composable private fun StatusChip(label: String, has: Boolean) { - Card( - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors( - containerColor = if (has) PrimaryBlue.copy(alpha = 0.1f) else MutedText.copy(alpha = 0.08f), - ), + ExpressiveGlassCard( + cornerRadius = 22.dp, + containerColor = if (has) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surfaceContainerHigh, ) { Row( modifier = Modifier.padding(horizontal = 16.dp, vertical = 10.dp), horizontalArrangement = Arrangement.spacedBy(6.dp), verticalAlignment = Alignment.CenterVertically, ) { - Text(label, fontSize = 13.sp, fontWeight = FontWeight.SemiBold, color = if (has) PrimaryBlue else MutedText) + Text(label, fontSize = 13.sp, fontWeight = FontWeight.SemiBold, color = if (has) MaterialTheme.colorScheme.onPrimaryContainer else MaterialTheme.colorScheme.onSurfaceVariant) if (has) { - Text("!", fontSize = 13.sp, fontWeight = FontWeight.Bold, color = AccentPink) + Text("!", fontSize = 13.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.secondary) } } } @@ -299,15 +308,11 @@ private fun PlayAveragesCard(plays: List, songDb: Song val trend = remember(validPlays) { validPlays.weeklyAverageTrend().takeLast(24) } val levelTrend = remember(validPlays, songDbReady) { validPlays.weeklyLevelTrend(songDb).takeLast(24) } - Card( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), - ) { + ExpressiveGlassCard(modifier = Modifier.fillMaxWidth(), cornerRadius = 26.dp) { Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { - Text("Archived Play Averages", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = DarkText) + Text("Archived Play Averages", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) if (validPlays.isEmpty()) { - Text("No archived score data yet", fontSize = 12.sp, color = MutedText) + Text("No archived score data yet", fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) return@Column } @@ -317,12 +322,12 @@ private fun PlayAveragesCard(plays: List, songDb: Song } if (trend.size >= 2) { - Text("Average Over Time", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = DarkText) + Text("Average Over Time", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.onSurface) TransparentLineChart(trend, modifier = Modifier.fillMaxWidth().height(165.dp)) } if (levelTrend.size >= 2) { - Text("Average Difficulty Over Time", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = DarkText) + Text("Average Difficulty Over Time", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.onSurface) TransparentLineChart(levelTrend, modifier = Modifier.fillMaxWidth().height(165.dp), unit = "") } } @@ -331,24 +336,21 @@ private fun PlayAveragesCard(plays: List, songDb: Song @Composable private fun ScoreRankDistributionCard(plays: List) { - val rankCounts = remember(plays) { plays.rankCounts() } - val modeCounts = remember(plays) { plays.modeCounts() } + val primaryColor = MaterialTheme.colorScheme.primary + val rankCounts = remember(plays, primaryColor) { plays.rankCounts(primaryColor) } + val modeCounts = remember(plays, primaryColor) { plays.modeCounts(primaryColor) } val diffAverages = remember(plays) { plays.filter { it.achievement > 0.0 }.difficultyAverages() } if (rankCounts.isEmpty() && modeCounts.isEmpty() && diffAverages.isEmpty()) return - Card( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), - ) { + ExpressiveGlassCard(modifier = Modifier.fillMaxWidth(), cornerRadius = 26.dp) { Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { - Text("Play Mix", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = DarkText) + Text("Play Mix", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) if (modeCounts.isNotEmpty() || diffAverages.size >= 3) { Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.Top) { if (modeCounts.isNotEmpty()) { Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(6.dp)) { - Text("Chart Type", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = DarkText) + Text("Chart Type", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.onSurface) TransparentPieChart(modeCounts, modifier = Modifier.fillMaxWidth().height(150.dp)) modeCounts.forEach { (label, count, color) -> LegendRow(label, count, color) @@ -357,7 +359,7 @@ private fun ScoreRankDistributionCard(plays: List) { } if (diffAverages.size >= 3) { Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(6.dp)) { - Text("Avg by Diff", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = DarkText) + Text("Avg by Diff", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.onSurface) DifficultyRadarChart(diffAverages, modifier = Modifier.fillMaxWidth().height(180.dp)) } } @@ -365,7 +367,7 @@ private fun ScoreRankDistributionCard(plays: List) { } if (rankCounts.isNotEmpty()) { - Text("Rank Distribution", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = DarkText) + Text("Rank Distribution", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.onSurface) TransparentBarChart(rankCounts, modifier = Modifier.fillMaxWidth().height(220.dp)) } } @@ -381,15 +383,15 @@ private fun DifficultyRadarChart(diffAverages: List>, modif scaleStepsCount = 4, maxLabelWidth = 42.dp, radarChartLineStyle = RadarChartLineStyle( - color = MutedText.copy(alpha = 0.25f), + color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.7f), strokeWidth = 1f, strokeCap = StrokeCap.Butt, ), radarChartDataSet = listOf( RadarChartDataSet( style = RadarChartDataSetStyle( - color = PrimaryBlue, - borderColor = AccentPink, + color = MaterialTheme.colorScheme.primary, + borderColor = MaterialTheme.colorScheme.secondary, colorAlpha = 0.2f, strokeWidth = 2f, strokeCap = StrokeCap.Round, @@ -397,7 +399,7 @@ private fun DifficultyRadarChart(diffAverages: List>, modif data = diffAverages.map { it.second }, ), ), - labelTextStyle = androidx.compose.ui.text.TextStyle(color = DarkText, fontSize = 9.sp, fontWeight = FontWeight.SemiBold), + labelTextStyle = androidx.compose.ui.text.TextStyle(color = MaterialTheme.colorScheme.onSurface, fontSize = 9.sp, fontWeight = FontWeight.SemiBold), scaleTextStyle = androidx.compose.ui.text.TextStyle(color = Color.Transparent, fontSize = 1.sp), scaleUnit = "%", maxScaleValue = 101.0, @@ -409,15 +411,17 @@ private fun DifficultyRadarChart(diffAverages: List>, modif @Composable private fun TransparentLineChart(trend: List, modifier: Modifier = Modifier, unit: String = "%") { Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { + val primaryColor = MaterialTheme.colorScheme.primary + val gridColor = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.5f) val minValue = trend.minOf { it.average }.coerceAtMost(100.0) val maxValue = trend.maxOf { it.average }.coerceAtLeast(minValue + 0.1) val axisMin = ((minValue - 0.25).coerceAtLeast(0.0) * 10).toInt() / 10.0 val axisMax = (((maxValue + 0.25).coerceAtMost(101.0) * 10).toInt() + 1) / 10.0 Row(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(6.dp)) { Column(modifier = Modifier.width(34.dp).fillMaxHeight().padding(top = 2.dp, bottom = 24.dp), verticalArrangement = Arrangement.SpaceBetween) { - Text("${formatScore(axisMax, 1)}$unit", fontSize = 9.sp, color = MutedText, textAlign = TextAlign.End, modifier = Modifier.fillMaxWidth()) - Text("${formatScore((axisMin + axisMax) / 2.0, 1)}$unit", fontSize = 9.sp, color = MutedText, textAlign = TextAlign.End, modifier = Modifier.fillMaxWidth()) - Text("${formatScore(axisMin, 1)}$unit", fontSize = 9.sp, color = MutedText, textAlign = TextAlign.End, modifier = Modifier.fillMaxWidth()) + Text("${formatScore(axisMax, 1)}$unit", fontSize = 9.sp, color = MaterialTheme.colorScheme.onSurfaceVariant, textAlign = TextAlign.End, modifier = Modifier.fillMaxWidth()) + Text("${formatScore((axisMin + axisMax) / 2.0, 1)}$unit", fontSize = 9.sp, color = MaterialTheme.colorScheme.onSurfaceVariant, textAlign = TextAlign.End, modifier = Modifier.fillMaxWidth()) + Text("${formatScore(axisMin, 1)}$unit", fontSize = 9.sp, color = MaterialTheme.colorScheme.onSurfaceVariant, textAlign = TextAlign.End, modifier = Modifier.fillMaxWidth()) } Canvas(modifier = Modifier.weight(1f).fillMaxHeight()) { val left = 0f @@ -427,7 +431,7 @@ private fun TransparentLineChart(trend: List, modifier: Modifier repeat(4) { i -> val y = top + ((bottom - top) * i / 3f) - drawLine(MutedText.copy(alpha = 0.16f), start = androidx.compose.ui.geometry.Offset(left, y), end = androidx.compose.ui.geometry.Offset(right, y), strokeWidth = 1.dp.toPx()) + drawLine(gridColor, start = androidx.compose.ui.geometry.Offset(left, y), end = androidx.compose.ui.geometry.Offset(right, y), strokeWidth = 1.dp.toPx()) } val points = trend.mapIndexed { index, bucket -> @@ -439,12 +443,12 @@ private fun TransparentLineChart(trend: List, modifier: Modifier moveTo(points.first().x, points.first().y) points.drop(1).forEach { lineTo(it.x, it.y) } } - drawPath(path, color = PrimaryBlue, style = Stroke(width = 3.dp.toPx(), cap = StrokeCap.Round)) + drawPath(path, color = primaryColor, style = Stroke(width = 3.dp.toPx(), cap = StrokeCap.Round)) } } Row(modifier = Modifier.fillMaxWidth().padding(start = 40.dp), horizontalArrangement = Arrangement.SpaceBetween) { - Text(trend.first().label, fontSize = 10.sp, color = MutedText) - Text(trend.last().label, fontSize = 10.sp, color = MutedText) + Text(trend.first().label, fontSize = 10.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) + Text(trend.last().label, fontSize = 10.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } } } @@ -452,6 +456,7 @@ private fun TransparentLineChart(trend: List, modifier: Modifier @Composable private fun TransparentPieChart(slices: List>, modifier: Modifier = Modifier) { val total = slices.sumOf { it.second }.coerceAtLeast(1) + val centerCutoutColor = MaterialTheme.colorScheme.surface.copy(alpha = 0.9f) Canvas(modifier = modifier) { val diameter = minOf(size.width, size.height) * 0.86f val topLeft = androidx.compose.ui.geometry.Offset((size.width - diameter) / 2f, (size.height - diameter) / 2f) @@ -461,13 +466,14 @@ private fun TransparentPieChart(slices: List>, modifi drawArc(color = color, startAngle = start, sweepAngle = sweep, useCenter = true, topLeft = topLeft, size = androidx.compose.ui.geometry.Size(diameter, diameter)) start += sweep } - drawCircle(CardWhite.copy(alpha = 0.9f), radius = diameter * 0.28f, center = center) + drawCircle(centerCutoutColor, radius = diameter * 0.28f, center = center) } } @Composable private fun TransparentBarChart(rankCounts: List, modifier: Modifier = Modifier) { Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { + val gridColor = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.5f) Box(modifier = modifier) { Canvas(modifier = Modifier.matchParentSize()) { val maxCount = (rankCounts.maxOfOrNull { it.count } ?: 1).coerceAtLeast(1) @@ -477,7 +483,7 @@ private fun TransparentBarChart(rankCounts: List, modifier: Modifier val barWidth = (slot * 0.55f).coerceAtMost(24.dp.toPx()) repeat(4) { i -> val y = top + ((bottom - top) * i / 3f) - drawLine(MutedText.copy(alpha = 0.14f), start = androidx.compose.ui.geometry.Offset(0f, y), end = androidx.compose.ui.geometry.Offset(size.width, y), strokeWidth = 1.dp.toPx()) + drawLine(gridColor, start = androidx.compose.ui.geometry.Offset(0f, y), end = androidx.compose.ui.geometry.Offset(size.width, y), strokeWidth = 1.dp.toPx()) } rankCounts.forEachIndexed { index, item -> val height = (bottom - top) * item.count / maxCount.toFloat() @@ -499,7 +505,7 @@ private fun TransparentBarChart(rankCounts: List, modifier: Modifier "${item.count}", fontSize = 10.sp, fontWeight = FontWeight.Bold, - color = DarkText, + color = MaterialTheme.colorScheme.onSurface, textAlign = TextAlign.Center, modifier = Modifier .align(Alignment.BottomCenter) @@ -514,7 +520,7 @@ private fun TransparentBarChart(rankCounts: List, modifier: Modifier } ?: Text( item.label, fontSize = 9.sp, - color = MutedText, + color = MaterialTheme.colorScheme.onSurfaceVariant, textAlign = TextAlign.Center, modifier = Modifier.align(Alignment.BottomCenter), ) @@ -528,9 +534,9 @@ private fun TransparentBarChart(rankCounts: List, modifier: Modifier @Composable private fun LegendRow(label: String, count: Int, color: Color) { Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) { - Surface(modifier = Modifier.size(10.dp), shape = RoundedCornerShape(2.dp), color = color, content = {}) - Text(label, fontSize = 12.sp, fontWeight = FontWeight.SemiBold, color = DarkText) - Text("$count", fontSize = 12.sp, color = MutedText) + ExpressiveDot(color = color) + Text(label, fontSize = 12.sp, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.onSurface) + Text("$count", fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } } @@ -626,17 +632,21 @@ private fun String.weekKey(): WeekBucketKey? { return WeekBucketKey(year, month, week) } -private fun List.modeCounts(): List> { +private fun List.modeCounts(primaryColor: Color): List> { val dx = count { it.isDx } val standard = size - dx + val colorsByMode = mapOf( + "DX" to primaryColor, + "Standard" to DiffAdvanced, + ) return listOf("DX" to dx, "Standard" to standard) .filter { it.second > 0 } - .map { (label, count) -> Triple(label, count, if (label == "DX") PrimaryBlue else AccentPink) } + .map { (label, count) -> Triple(label, count, colorsByMode[label] ?: primaryColor) } } -private fun List.rankCounts(): List { +private fun List.rankCounts(primaryColor: Color): List { val order = listOf("sssp", "sss", "ssp", "ss", "sp", "s", "aaa", "aa", "a", "bbb", "bb", "b", "c", "d") - val colors = listOf(AccentPink, PrimaryBlue, DiffMaster, DiffExpert, DiffAdvanced, DiffBasic) + val colors = listOf(primaryColor, DiffMaster, DiffExpert, DiffAdvanced, DiffBasic) return groupBy { it.scoreRank.normalizedRank() } .filterKeys { it.isNotBlank() } .mapValues { (_, plays) -> plays.size } diff --git a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/PlayerNameTab.kt b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/PlayerNameTab.kt index 5304606..c71fadb 100644 --- a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/PlayerNameTab.kt +++ b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/PlayerNameTab.kt @@ -2,7 +2,6 @@ package com.derakkuma.ui.tabs import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Check @@ -17,6 +16,12 @@ import com.derakkuma.atproto.AtProtoClient import com.derakkuma.atproto.syncCachedProfileToAtProto import com.derakkuma.data.* import com.derakkuma.ui.components.EmptyState +import com.derakkuma.ui.components.ExpressiveGlassCard +import com.derakkuma.ui.components.ExpressiveTextButton +import com.derakkuma.ui.components.ExpressiveSnackbar +import com.derakkuma.ui.components.ExpressiveScaffold +import com.derakkuma.ui.components.ExpressiveFloatingActionButton +import com.derakkuma.ui.components.ExpressiveTextField import com.derakkuma.ui.components.LoadingState import com.derakkuma.ui.theme.* import kotlinx.coroutines.launch @@ -53,22 +58,21 @@ fun PlayerNameTab( loading = false } - Scaffold( + ExpressiveScaffold( contentWindowInsets = WindowInsets(0.dp), snackbarHost = { if (snackMsg != null) { - Snackbar( + ExpressiveSnackbar( modifier = Modifier.padding(12.dp), - shape = RoundedCornerShape(8.dp), - action = { TextButton(onClick = { snackMsg = null }) { Text("OK") } }, + action = { ExpressiveTextButton(onClick = { snackMsg = null }) { Text("OK") } }, ) { Text(snackMsg!!) } } }, floatingActionButton = { if (dirty && !saving && !maintenanceMode) { - FloatingActionButton( + ExpressiveFloatingActionButton( onClick = { - val settings = nameSettings ?: return@FloatingActionButton + val settings = nameSettings ?: return@ExpressiveFloatingActionButton saving = true scope.launch { try { @@ -89,9 +93,9 @@ fun PlayerNameTab( saving = false } }, - containerColor = PrimaryBlue, + containerColor = MaterialTheme.colorScheme.primary, ) { - Icon(Icons.Default.Check, contentDescription = "Save", tint = CardWhite) + Icon(Icons.Default.Check, contentDescription = "Save", tint = MaterialTheme.colorScheme.onPrimary) } } }, @@ -110,18 +114,16 @@ fun PlayerNameTab( } else if (nameSettings != null) { val ns = nameSettings!! - Card( + ExpressiveGlassCard( modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), ) { Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { - Text("Player Name", fontSize = 20.sp, fontWeight = FontWeight.Bold, color = DarkText) + Text("Player Name", fontSize = 20.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) - OutlinedTextField( + ExpressiveTextField( value = newName, onValueChange = { - if (maintenanceMode) return@OutlinedTextField + if (maintenanceMode) return@ExpressiveTextField if (it.length <= ns.maxLength) { newName = it dirty = it != ns.currentName @@ -132,11 +134,10 @@ fun PlayerNameTab( singleLine = true, enabled = !maintenanceMode, supportingText = { Text("${newName.length} / ${ns.maxLength}") }, - shape = RoundedCornerShape(8.dp), - ) + ) if (ns.allowedSymbols.isNotEmpty()) { - Text("Additional Symbols", fontSize = 13.sp, fontWeight = FontWeight.SemiBold, color = MutedText) + Text("Additional Symbols", fontSize = 13.sp, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.onSurfaceVariant) // Symbol grid FlowRow( modifier = Modifier.fillMaxWidth(), @@ -144,9 +145,9 @@ fun PlayerNameTab( verticalArrangement = Arrangement.spacedBy(4.dp), ) { ns.allowedSymbols.forEach { symbol -> - TextButton( + ExpressiveTextButton( onClick = { - if (maintenanceMode) return@TextButton + if (maintenanceMode) return@ExpressiveTextButton if (newName.length + symbol.length <= ns.maxLength) { newName += symbol dirty = newName != ns.currentName @@ -156,7 +157,7 @@ fun PlayerNameTab( enabled = !maintenanceMode, contentPadding = PaddingValues(0.dp), ) { - Text(symbol, fontSize = 16.sp, color = PrimaryBlue) + Text(symbol, fontSize = 16.sp, color = MaterialTheme.colorScheme.primary) } } } diff --git a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/RankingTab.kt b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/RankingTab.kt index 838ef47..9a9bbea 100644 --- a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/RankingTab.kt +++ b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/RankingTab.kt @@ -3,6 +3,7 @@ package com.derakkuma.ui.tabs import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.* import androidx.compose.runtime.* @@ -15,6 +16,13 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.derakkuma.data.* import com.derakkuma.ui.components.ErrorState +import com.derakkuma.ui.components.ExpressiveButton +import com.derakkuma.ui.components.ExpressiveGlassCard +import com.derakkuma.ui.components.ExpressiveBadgeSurface +import com.derakkuma.ui.components.ExpressiveFilterChip +import com.derakkuma.ui.components.ExpressiveTextButton +import com.derakkuma.ui.components.ExpressiveDropdownMenu +import com.derakkuma.ui.components.ExpressiveDropdownMenuItem import com.derakkuma.ui.components.LoadingState import com.derakkuma.ui.theme.* import com.derakkuma.util.formatScore @@ -124,24 +132,14 @@ fun RankingTab(client: MaimaiClient, cookie: String, onSessionExpired: (() -> Un Column(Modifier.fillMaxSize()) { // Sub-section tabs - PrimaryScrollableTabRow( - selectedTabIndex = section.ordinal, - containerColor = Color.Transparent, - contentColor = Color.White, - edgePadding = 1.dp, - divider = {}, - ) { - RankingSection.entries.forEach { s -> - Tab( - selected = section == s, - onClick = { section = s }, - text = { Text(s.label, fontSize = 12.sp, fontWeight = if (section == s) FontWeight.Bold else FontWeight.Normal) }, - ) - } - } + RankingSectionTabs( + selected = section, + onSelect = { section = it }, + modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 8.dp), + ) if (loading) { - LoadingState(Modifier.fillMaxSize(), color = PrimaryBlue) + LoadingState(Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.primary) } else if (error != null) { ErrorState(error!!, onRetry = { reload() }) } else { @@ -208,28 +206,76 @@ fun RankingTab(client: MaimaiClient, cookie: String, onSessionExpired: (() -> Un } } +@Composable +private fun RankingSectionTabs( + selected: RankingSection, + onSelect: (RankingSection) -> Unit, + modifier: Modifier = Modifier, +) { + ExpressiveGlassCard(modifier = modifier, cornerRadius = 26.dp) { + Column( + modifier = Modifier.fillMaxWidth().padding(12.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + Text("Ranking board", fontSize = 12.sp, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.onSurfaceVariant) + FlowRow( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(6.dp), + verticalArrangement = Arrangement.spacedBy(6.dp), + ) { + RankingSection.entries.forEach { option -> + RankingExpressiveFilterChip( + selected = option == selected, + selectedColor = when (option) { + RankingSection.Song -> MaterialTheme.colorScheme.primary + RankingSection.Course -> MaterialTheme.colorScheme.tertiary + RankingSection.Season -> MaterialTheme.colorScheme.secondary + RankingSection.DeluxeRating -> MaterialTheme.colorScheme.error + RankingSection.Total -> MaterialTheme.colorScheme.primary + RankingSection.Partner -> MaterialTheme.colorScheme.tertiary + RankingSection.Finale -> MaterialTheme.colorScheme.secondary + }, + onClick = { onSelect(option) }, + ) { + Text(option.label, fontSize = 11.sp, fontWeight = if (option == selected) FontWeight.Bold else FontWeight.SemiBold, maxLines = 1) + } + } + } + } + } +} + // ---- Filter Chips ---- @Composable private fun FilterChipRow(label: String, options: List, selected: String, onSelect: (String) -> Unit) { if (options.isEmpty()) return Column(Modifier.fillMaxWidth().padding(vertical = 4.dp)) { - Text(label, fontSize = 12.sp, fontWeight = FontWeight.SemiBold, color = MutedText, modifier = Modifier.padding(start = 4.dp, bottom = 4.dp)) + Text(label, fontSize = 12.sp, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.padding(start = 4.dp, bottom = 4.dp)) Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(6.dp)) { options.take(8).forEach { opt -> - RankingFilterChip(selected = opt.value == selected, onClick = { onSelect(opt.value) }) { Text(opt.label, fontSize = 11.sp, maxLines = 1) } + RankingExpressiveFilterChip(selected = opt.value == selected, onClick = { onSelect(opt.value) }) { Text(opt.label, fontSize = 11.sp, maxLines = 1) } } if (options.size > 8) { // Show dropdown for too many options var expanded by remember { mutableStateOf(false) } Box { - TextButton(onClick = { expanded = true }, modifier = Modifier.height(32.dp)) { - Text("More...", fontSize = 11.sp, color = PrimaryBlue) + ExpressiveTextButton(onClick = { expanded = true }, modifier = Modifier.height(32.dp)) { + Text("More...", fontSize = 11.sp, color = MaterialTheme.colorScheme.primary) } - DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) { - options.drop(8).forEach { opt -> - DropdownMenuItem( - text = { Text(opt.label, fontSize = 12.sp) }, + ExpressiveDropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + ) { + val extraOptions = options.drop(8) + extraOptions.forEachIndexed { index, opt -> + val isSelected = opt.value == selected + ExpressiveDropdownMenuItem( + selected = isSelected, + text = { Text(opt.label, fontSize = 12.sp, fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal) }, + leadingIcon = if (isSelected) ({ Text("✓", color = MaterialTheme.colorScheme.primary, fontWeight = FontWeight.Bold) }) else null, + index = index, + lastIndex = extraOptions.lastIndex, onClick = { onSelect(opt.value) expanded = false @@ -244,24 +290,17 @@ private fun FilterChipRow(label: String, options: List, sel } @Composable -private fun RankingFilterChip(selected: Boolean, onClick: () -> Unit, label: @Composable () -> Unit) { - FilterChip( +private fun RankingExpressiveFilterChip( + selected: Boolean, + onClick: () -> Unit, + selectedColor: Color = MaterialTheme.colorScheme.primary, + label: @Composable () -> Unit, +) { + ExpressiveFilterChip( selected = selected, onClick = onClick, label = label, - modifier = Modifier.height(32.dp), - colors = FilterChipDefaults.filterChipColors( - containerColor = Color.White, - labelColor = DarkText, - selectedContainerColor = PrimaryBlue, - selectedLabelColor = Color.White, - ), - border = FilterChipDefaults.filterChipBorder( - enabled = true, - selected = selected, - borderColor = Color.Transparent, - selectedBorderColor = PrimaryBlue, - ), + selectedColor = selectedColor, ) } @@ -271,30 +310,30 @@ private fun DiffChipRow(options: List, selected: String, on Row(Modifier.fillMaxWidth().padding(vertical = 4.dp), horizontalArrangement = Arrangement.spacedBy(6.dp)) { options.forEach { opt -> val chipColor = when (opt.value) { - "0" -> Color(0xFF22A7F0) + "0" -> MaterialTheme.colorScheme.primary // Basic - blue - "1" -> Color(0xFF2ECC71) + "1" -> DiffBasic // Advanced - green - "2" -> Color(0xFFFFC312) + "2" -> DiffAdvanced // Expert - yellow - "3" -> Color(0xFFE74C3C) + "3" -> DiffExpert // Master - red - "4" -> Color(0xFF9B59B6) + "4" -> DiffMaster // Re:MASTER - purple - "10" -> Color(0xFFE91E63) + "10" -> DiffRemaster // Utage - pink - "99" -> Color(0xFF7F8C8D) + "99" -> MaterialTheme.colorScheme.outline // All - gray - else -> PrimaryBlue + else -> MaterialTheme.colorScheme.primary } - RankingFilterChip(selected = opt.value == selected, onClick = { onSelect(opt.value) }) { Text(opt.label, fontSize = 11.sp, maxLines = 1) } + RankingExpressiveFilterChip(selected = opt.value == selected, selectedColor = chipColor, onClick = { onSelect(opt.value) }) { Text(opt.label, fontSize = 11.sp, maxLines = 1) } } } } @@ -317,7 +356,7 @@ private fun ScoreTypeChipRow(options: List, selected: Strin @Composable private fun ApplyButton(onClick: () -> Unit) { - Button(onClick = onClick, modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp), shape = RoundedCornerShape(8.dp), colors = ButtonDefaults.buttonColors(containerColor = PrimaryBlue)) { + ExpressiveButton(onClick = onClick, modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp), colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary)) { Text("Apply Filters", fontWeight = FontWeight.SemiBold) } } @@ -329,22 +368,22 @@ private fun PlayerRankingList(entries: List, yourRank: Strin LazyColumn(Modifier.fillMaxSize(), contentPadding = PaddingValues(start = 12.dp, end = 12.dp, top = 8.dp, bottom = 88.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) { if (yourRank.isNotBlank() && yourRank != "―") { item(key = "your-rank") { - Card(Modifier.fillMaxWidth(), shape = RoundedCornerShape(8.dp), colors = CardDefaults.cardColors(containerColor = PrimaryBlue.copy(alpha = 0.1f))) { + ExpressiveGlassCard(Modifier.fillMaxWidth(), cornerRadius = 22.dp, containerColor = MaterialTheme.colorScheme.primaryContainer) { Row(Modifier.fillMaxWidth().padding(12.dp), verticalAlignment = Alignment.CenterVertically) { - Text("Your Rank: ", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = PrimaryBlue) - Text(yourRank, fontSize = 14.sp, fontWeight = FontWeight.Bold, color = PrimaryBlue) + Text("Your Rank: ", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.onPrimaryContainer) + Text(yourRank, fontSize = 14.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onPrimaryContainer) } } } } if (updatedAt.isNotBlank()) { item(key = "updated") { - Text("Updated: $updatedAt", fontSize = 11.sp, color = MutedText, modifier = Modifier.padding(start = 4.dp, bottom = 4.dp)) + Text("Updated: $updatedAt", fontSize = 11.sp, color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.padding(start = 4.dp, bottom = 4.dp)) } } if (headerLabel.isNotBlank()) { item(key = "header-label") { - Text(headerLabel, fontSize = 14.sp, fontWeight = FontWeight.Bold, color = DarkText, modifier = Modifier.padding(start = 4.dp, bottom = 4.dp)) + Text(headerLabel, fontSize = 14.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface, modifier = Modifier.padding(start = 4.dp, bottom = 4.dp)) } } items(entries, key = { "pr-${it.rank}-${it.name}" }) { entry -> @@ -356,13 +395,13 @@ private fun PlayerRankingList(entries: List, yourRank: Strin @Composable private fun PlayerRankingRow(entry: PlayerRankingEntry) { val rankColor = when { - entry.rank == 1 -> Color(0xFFFFD700) - entry.rank == 2 -> Color(0xFFC0C0C0) - entry.rank == 3 -> Color(0xFFCD7F32) - entry.isTop3 -> Color(0xFFFFD700) + entry.rank == 1 -> MedalGold + entry.rank == 2 -> MedalSilver + entry.rank == 3 -> MedalBronze + entry.isTop3 -> MedalGold else -> MutedText.copy(alpha = 0.3f) } - Card(Modifier.fillMaxWidth(), shape = RoundedCornerShape(8.dp), colors = CardDefaults.cardColors(containerColor = CardWhite)) { + ExpressiveGlassCard(Modifier.fillMaxWidth(), cornerRadius = 28.dp) { Row(Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 8.dp), verticalAlignment = Alignment.CenterVertically) { Surface(Modifier.size(28.dp), shape = RoundedCornerShape(6.dp), color = rankColor) { Box(contentAlignment = Alignment.Center) { @@ -370,9 +409,9 @@ private fun PlayerRankingRow(entry: PlayerRankingEntry) { } } Spacer(Modifier.width(8.dp)) - Text(entry.name, fontSize = 14.sp, fontWeight = if (entry.isTop3) FontWeight.SemiBold else FontWeight.Normal, color = DarkText, modifier = Modifier.weight(1f), maxLines = 1, overflow = TextOverflow.Ellipsis) + Text(entry.name, fontSize = 14.sp, fontWeight = if (entry.isTop3) FontWeight.SemiBold else FontWeight.Normal, color = MaterialTheme.colorScheme.onSurface, modifier = Modifier.weight(1f), maxLines = 1, overflow = TextOverflow.Ellipsis) Spacer(Modifier.width(8.dp)) - Text(entry.score, fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = if (entry.isTop3) PrimaryBlue else MutedText) + Text(entry.score, fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = if (entry.isTop3) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant) } } } @@ -430,7 +469,7 @@ private fun SongRankingSection( if (song.genre != lastGenre && song.genre.isNotBlank()) { lastGenre = song.genre item(key = "genre-$i") { - Text(song.genre, fontSize = 14.sp, fontWeight = FontWeight.Bold, color = PrimaryBlue, modifier = Modifier.padding(start = 4.dp, top = 8.dp, bottom = 4.dp)) + Text(song.genre, fontSize = 14.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.primary, modifier = Modifier.padding(start = 4.dp, top = 8.dp, bottom = 4.dp)) } } item(key = "song-$i") { @@ -440,7 +479,7 @@ private fun SongRankingSection( } } else if (data != null && data.songs.isEmpty()) { Box(Modifier.fillMaxSize().padding(24.dp), contentAlignment = Alignment.Center) { - Text("No songs found for these filters", fontSize = 14.sp, color = MutedText) + Text("No songs found for these filters", fontSize = 14.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } } } @@ -455,7 +494,7 @@ private fun SongRankingRow(song: SongRankingEntry) { "remaster" -> Color(0xFF9B59B6) else -> MutedText } - Card(Modifier.fillMaxWidth(), shape = RoundedCornerShape(8.dp), colors = CardDefaults.cardColors(containerColor = CardWhite)) { + ExpressiveGlassCard(Modifier.fillMaxWidth(), cornerRadius = 28.dp) { Row(Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 8.dp), verticalAlignment = Alignment.CenterVertically) { // Difficulty badge Surface(Modifier.size(28.dp), shape = RoundedCornerShape(6.dp), color = diffColor.copy(alpha = 0.2f)) { @@ -465,12 +504,12 @@ private fun SongRankingRow(song: SongRankingEntry) { } Spacer(Modifier.width(8.dp)) // Song name - Text(song.name, fontSize = 14.sp, fontWeight = FontWeight.Normal, color = DarkText, modifier = Modifier.weight(1f), maxLines = 1, overflow = TextOverflow.Ellipsis) + Text(song.name, fontSize = 14.sp, fontWeight = FontWeight.Normal, color = MaterialTheme.colorScheme.onSurface, modifier = Modifier.weight(1f), maxLines = 1, overflow = TextOverflow.Ellipsis) // DX badge if (song.isDx) { Spacer(Modifier.width(4.dp)) - Surface(shape = RoundedCornerShape(4.dp), color = Color(0xFFE91E63).copy(alpha = 0.15f)) { - Text("DX", fontSize = 10.sp, fontWeight = FontWeight.Bold, color = Color(0xFFE91E63), modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp)) + ExpressiveBadgeSurface(shape = MaterialTheme.shapes.large, containerColor = MaterialTheme.colorScheme.secondaryContainer) { + Text("DX", fontSize = 10.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSecondaryContainer, modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp)) } } } @@ -567,16 +606,16 @@ private fun DeluxeRatingSection( // Your rating if (data.yourRating > 0) { item(key = "your-rating") { - Card(Modifier.fillMaxWidth(), shape = RoundedCornerShape(8.dp), colors = CardDefaults.cardColors(containerColor = PrimaryBlue.copy(alpha = 0.1f))) { + ExpressiveGlassCard(Modifier.fillMaxWidth(), cornerRadius = 22.dp, containerColor = MaterialTheme.colorScheme.primaryContainer) { Row(Modifier.fillMaxWidth().padding(12.dp), verticalAlignment = Alignment.CenterVertically) { - Text("Your DX Rating: ", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = PrimaryBlue) - Text("${data.yourRating}", fontSize = 14.sp, fontWeight = FontWeight.Bold, color = PrimaryBlue) + Text("Your DX Rating: ", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.onPrimaryContainer) + Text("${data.yourRating}", fontSize = 14.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onPrimaryContainer) } } } } if (data.updatedAt.isNotBlank()) { - item(key = "updated") { Text("Updated: ${data.updatedAt}", fontSize = 11.sp, color = MutedText, modifier = Modifier.padding(start = 4.dp, bottom = 4.dp)) } + item(key = "updated") { Text("Updated: ${data.updatedAt}", fontSize = 11.sp, color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.padding(start = 4.dp, bottom = 4.dp)) } } items(data.entries, key = { "dx-${it.rank}-${it.name}" }) { entry -> PlayerRankingRow(entry) } } diff --git a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/RatingTab.kt b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/RatingTab.kt index 738f5ef..db659cc 100644 --- a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/RatingTab.kt +++ b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/RatingTab.kt @@ -3,9 +3,7 @@ package com.derakkuma.ui.tabs import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.* -import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -21,6 +19,9 @@ import com.derakkuma.data.RatingData import com.derakkuma.data.RatingSong import com.derakkuma.data.SessionExpiredException import com.derakkuma.ui.components.ErrorState +import com.derakkuma.ui.components.ExpressiveGlassCard +import com.derakkuma.ui.components.ExpressivePullToRefreshBox +import com.derakkuma.ui.components.ExpressiveHorizontalDivider import com.derakkuma.ui.components.LoadingState import com.derakkuma.ui.components.SongRow import com.derakkuma.ui.theme.* @@ -64,10 +65,10 @@ fun RatingTab(client: MaimaiClient, cookie: String, maintenanceMode: Boolean = f isRefreshing = false } - PullToRefreshBox( + ExpressivePullToRefreshBox( isRefreshing = isRefreshing, onRefresh = { - if (maintenanceMode) return@PullToRefreshBox + if (maintenanceMode) return@ExpressivePullToRefreshBox isRefreshing = true refreshKey += 1 }, @@ -83,22 +84,14 @@ fun RatingTab(client: MaimaiClient, cookie: String, maintenanceMode: Boolean = f LazyColumn(modifier = Modifier.fillMaxSize(), contentPadding = PaddingValues(start = 12.dp, end = 12.dp, top = 6.dp, bottom = 88.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { // New Songs section item { - Card( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), - ) { - Text("New Songs (${newSongs.size})", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = DarkText, modifier = Modifier.padding(10.dp).padding(bottom = 4.dp)) + ExpressiveGlassCard(modifier = Modifier.fillMaxWidth(), cornerRadius = 22.dp) { + Text("New Songs (${newSongs.size})", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface, modifier = Modifier.padding(10.dp).padding(bottom = 4.dp)) } } itemsIndexed(newSongs, key = { index, song -> "new-$index-${song.name}-${song.difficulty}" }) { i, song -> - Card( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), - ) { + ExpressiveGlassCard(modifier = Modifier.fillMaxWidth(), cornerRadius = 22.dp) { Column(modifier = Modifier.padding(horizontal = 10.dp)) { - if (i > 0) HorizontalDivider(color = MutedText.copy(alpha = 0.15f)) + if (i > 0) ExpressiveHorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.6f)) RatingSongRow(song) } } @@ -106,22 +99,14 @@ fun RatingTab(client: MaimaiClient, cookie: String, maintenanceMode: Boolean = f // Other Songs section item { - Card( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), - ) { - Text("Other Songs (${otherSongs.size})", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = DarkText, modifier = Modifier.padding(10.dp).padding(bottom = 4.dp)) + ExpressiveGlassCard(modifier = Modifier.fillMaxWidth(), cornerRadius = 22.dp) { + Text("Other Songs (${otherSongs.size})", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface, modifier = Modifier.padding(10.dp).padding(bottom = 4.dp)) } } itemsIndexed(otherSongs, key = { index, song -> "other-$index-${song.name}-${song.difficulty}" }) { i, song -> - Card( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), - ) { + ExpressiveGlassCard(modifier = Modifier.fillMaxWidth(), cornerRadius = 22.dp) { Column(modifier = Modifier.padding(horizontal = 10.dp)) { - if (i > 0) HorizontalDivider(color = MutedText.copy(alpha = 0.15f)) + if (i > 0) ExpressiveHorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.6f)) RatingSongRow(song) } } diff --git a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/ScoresTab.kt b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/ScoresTab.kt index bb02793..fdf2883 100644 --- a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/ScoresTab.kt +++ b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/ScoresTab.kt @@ -5,11 +5,9 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Sync import androidx.compose.material3.* -import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -30,6 +28,13 @@ import com.derakkuma.data.RatingData import com.derakkuma.data.RecentPlay import com.derakkuma.data.SessionExpiredException import com.derakkuma.ui.components.ErrorState +import com.derakkuma.ui.components.ExpressiveButton +import com.derakkuma.ui.components.ExpressiveGlassCard +import com.derakkuma.ui.components.ExpressivePullToRefreshBox +import com.derakkuma.ui.components.ExpressiveHorizontalDivider +import com.derakkuma.ui.components.ExpressiveBadgeSurface +import com.derakkuma.ui.components.ExpressiveLinearProgressIndicator +import com.derakkuma.ui.components.ExpressiveLoadingIndicator import com.derakkuma.ui.components.LoadingState import com.derakkuma.ui.components.SongRow import com.derakkuma.ui.images.BundledIcon @@ -141,10 +146,10 @@ fun ScoresTab( historicalPlays = ps.getHistoricalPlaysForDisplay() } - PullToRefreshBox( + ExpressivePullToRefreshBox( isRefreshing = isRefreshing, onRefresh = { - if (maintenanceMode) return@PullToRefreshBox + if (maintenanceMode) return@ExpressivePullToRefreshBox isRefreshing = true refreshKey += 1 }, @@ -189,46 +194,48 @@ fun ScoresTab( ) { if (loading) { item { - LinearProgressIndicator( + ExpressiveLinearProgressIndicator( modifier = Modifier.fillMaxWidth().height(2.dp), - color = PrimaryBlue, + color = MaterialTheme.colorScheme.primary, trackColor = Color.Transparent, ) } } - // Stats summary + // Stats summary. Keep the original records summary as the first real content + // item so Current Ver / Dashboard / Archived / maimile remain visible at top. item { - Card( + ExpressiveGlassCard( modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), + cornerRadius = 28.dp, + containerColor = Color.White, ) { - Row( + Column( modifier = Modifier.fillMaxWidth().padding(12.dp), - horizontalArrangement = Arrangement.SpaceEvenly, + verticalArrangement = Arrangement.spacedBy(12.dp), ) { - StatItem("Current Ver", "${data!!.playCount.currentVersion}") - StatItem("Dashboard", "${data!!.playCount.total}") - StatItem( - "Archived", - "${historicalPlays.size}", - showSyncChip = historicalPlays.isEmpty(), - onClick = { scope.launch { listState.animateScrollToItem(historicalSectionIndex) } }, - ) - StatItem("maimile", "${data!!.maimile}", useMaimileIcon = true) - } + Text("Play Statistics", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly, + ) { + StatItem("Current Ver", "${data!!.playCount.currentVersion}") + StatItem("Dashboard", "${data!!.playCount.total}") + StatItem( + "Archived", + "${historicalPlays.size}", + showSyncChip = historicalPlays.isEmpty(), + onClick = { scope.launch { listState.animateScrollToItem(historicalSectionIndex) } }, + ) + StatItem("maimile", "${data!!.maimile}", useMaimileIcon = true) + } - // Rating target music shortcut - Column( - modifier = Modifier.fillMaxWidth().padding(10.dp), - verticalArrangement = Arrangement.spacedBy(8.dp), - ) { - Button( + // Rating target music shortcut + ExpressiveButton( onClick = { onRatingTargetClick?.invoke() }, enabled = onRatingTargetClick != null, modifier = Modifier.fillMaxWidth(), - colors = ButtonDefaults.buttonColors(containerColor = PrimaryBlue), + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary), ) { Text("View DX Rating Calculation Records") } @@ -238,22 +245,14 @@ fun ScoresTab( // Recent plays header item { - Card( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), - ) { - Text("Recent Plays (${data!!.playLog.size})", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = DarkText, modifier = Modifier.padding(10.dp).padding(bottom = 4.dp)) + ExpressiveGlassCard(modifier = Modifier.fillMaxWidth(), cornerRadius = 28.dp) { + Text("Recent Plays (${data!!.playLog.size})", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface, modifier = Modifier.padding(10.dp).padding(bottom = 4.dp)) } } // Recent play items (last 50 from dashboard) items(recentPlays, key = { "${it.datetime}:${it.name}:${it.difficulty}" }) { play -> - Card( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), - ) { + ExpressiveGlassCard(modifier = Modifier.fillMaxWidth(), cornerRadius = 28.dp) { Row( modifier = Modifier .fillMaxWidth() @@ -284,16 +283,12 @@ fun ScoresTab( // Historical plays (backfilled from before 90 days) item { - HorizontalDivider( + ExpressiveHorizontalDivider( modifier = Modifier.padding(vertical = 8.dp), - color = MutedText.copy(alpha = 0.2f), + color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.7f), ) - Card( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), - ) { - Text("Historical Plays (${historicalFiltered.size})", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = DarkText, modifier = Modifier.padding(10.dp).padding(bottom = 4.dp)) + ExpressiveGlassCard(modifier = Modifier.fillMaxWidth(), cornerRadius = 28.dp) { + Text("Historical Plays (${historicalFiltered.size})", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface, modifier = Modifier.padding(10.dp).padding(bottom = 4.dp)) } } if (historicalFiltered.isEmpty()) { @@ -329,11 +324,7 @@ fun ScoresTab( } } else { items(historicalFiltered, key = { "h:${it.datetime}:${it.name}:${it.difficulty}" }) { play -> - Card( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), - ) { + ExpressiveGlassCard(modifier = Modifier.fillMaxWidth(), cornerRadius = 28.dp) { Row( modifier = Modifier .fillMaxWidth() @@ -371,10 +362,10 @@ private fun StatItem(label: String, value: String, useMaimileIcon: Boolean = fal horizontalAlignment = Alignment.CenterHorizontally, ) { Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) { - Text(value, fontSize = 18.sp, fontWeight = FontWeight.Bold, color = DarkText) + Text(value, fontSize = 18.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) if (showSyncChip) { - Surface(shape = RoundedCornerShape(999.dp), color = DangerRed) { - Icon(Icons.Default.Sync, contentDescription = "Sync historical plays", modifier = Modifier.padding(horizontal = 5.dp, vertical = 2.dp).size(12.dp), tint = Color.White) + ExpressiveBadgeSurface(containerColor = MaterialTheme.colorScheme.error) { + Icon(Icons.Default.Sync, contentDescription = "Sync historical plays", modifier = Modifier.padding(horizontal = 5.dp, vertical = 2.dp).size(12.dp), tint = MaterialTheme.colorScheme.onError) } } } @@ -385,10 +376,10 @@ private fun StatItem(label: String, value: String, useMaimileIcon: Boolean = fal modifier = Modifier.height(12.dp), contentDescription = null, ) - Text(label, fontSize = 11.sp, color = MutedText) + Text(label, fontSize = 11.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } } else { - Text(label, fontSize = 11.sp, color = MutedText) + Text(label, fontSize = 11.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } } } @@ -401,37 +392,32 @@ private fun HistoricalBackfillCard( result: HistoricalCrawler.Result?, onStart: () -> Unit, ) { - Card( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), - ) { + ExpressiveGlassCard(modifier = Modifier.fillMaxWidth(), cornerRadius = 26.dp) { Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { - Text("Historical Play Data", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = DarkText) - Text("Backfill your complete play history from before the 90-day window. This crawls your song list across all difficulties.", fontSize = 12.sp, color = MutedText) + Text("Historical Play Data", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) + Text("Backfill your complete play history from before the 90-day window. This crawls your song list across all difficulties.", fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) if (backfilling && progress != null) { val fraction = progress.current.toFloat() / progress.total.coerceAtLeast(1).toFloat() - LinearProgressIndicator( + ExpressiveLinearProgressIndicator( progress = { fraction.coerceIn(0f, 1f) }, modifier = Modifier.fillMaxWidth(), - color = PrimaryBlue, + color = MaterialTheme.colorScheme.primary, ) - Text(progress.status, fontSize = 12.sp, color = MutedText) - Text("Please keep the app open and on this screen until complete.", fontSize = 11.sp, color = MutedText, fontWeight = FontWeight.Medium) + Text(progress.status, fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) + Text("Please keep the app open and on this screen until complete.", fontSize = 11.sp, color = MaterialTheme.colorScheme.onSurfaceVariant, fontWeight = FontWeight.Medium) } else if (result != null) { - Text("Done! ${result.totalPlays} plays from ${result.totalSongs} songs.", fontSize = 13.sp, color = DarkText, fontWeight = FontWeight.Medium) + Text("Done! ${result.totalPlays} plays from ${result.totalSongs} songs.", fontSize = 13.sp, color = MaterialTheme.colorScheme.onSurface, fontWeight = FontWeight.Medium) } else if (error != null) { - Text(error, fontSize = 12.sp, color = DangerRed) + Text(error, fontSize = 12.sp, color = MaterialTheme.colorScheme.error) } - Button( + ExpressiveButton( onClick = onStart, modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = ButtonDefaults.buttonColors(containerColor = PrimaryBlue), + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary), enabled = !backfilling, ) { if (backfilling) { - CircularProgressIndicator(modifier = Modifier.size(16.dp), color = CardWhite, strokeWidth = 2.dp) + ExpressiveLoadingIndicator(modifier = Modifier, color = MaterialTheme.colorScheme.onPrimary, size = 16.dp) Spacer(Modifier.width(8.dp)) } Text(if (backfilling) "Backfilling..." else "Backfill Historical Data", fontWeight = FontWeight.SemiBold) diff --git a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/SettingsTab.kt b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/SettingsTab.kt index 5e6b334..922a123 100644 --- a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/SettingsTab.kt +++ b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/tabs/SettingsTab.kt @@ -1,11 +1,20 @@ package com.derakkuma.ui.tabs +import com.derakkuma.ui.components.ExpressiveSlider +import com.derakkuma.ui.components.ExpressiveLinearProgressIndicator +import com.derakkuma.ui.components.ExpressiveGlassCard +import com.derakkuma.ui.components.ExpressiveHorizontalDivider +import com.derakkuma.ui.components.ExpressiveTextButton +import com.derakkuma.ui.components.ExpressiveDropdownMenu +import com.derakkuma.ui.components.ExpressiveDropdownMenuItem +import com.derakkuma.ui.components.ExpressiveSnackbar +import com.derakkuma.ui.components.ExpressiveScaffold +import com.derakkuma.ui.components.ExpressiveFloatingActionButton +import com.derakkuma.ui.components.ExpressiveSwitch import androidx.compose.foundation.Image -import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Check @@ -18,7 +27,6 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.derakkuma.data.* @@ -31,7 +39,7 @@ import com.derakkuma.ui.images.MaimaiAssets import com.derakkuma.ui.images.rememberBundledImage import com.derakkuma.ui.theme.* import kotlinx.coroutines.launch -import com.derakkuma.ui.components.SplitButtonRow as SharedSplitButtonRow +import com.derakkuma.ui.components.SplitButtonRow @Composable fun SettingsTab(client: MaimaiClient, cookie: String, maintenanceMode: Boolean = false) { @@ -62,26 +70,23 @@ fun SettingsTab(client: MaimaiClient, cookie: String, maintenanceMode: Boolean = loading = false } - Scaffold( + ExpressiveScaffold( contentWindowInsets = WindowInsets(0.dp), snackbarHost = { if (snackMsg != null) { - Snackbar( + ExpressiveSnackbar( modifier = Modifier.padding(12.dp), - shape = RoundedCornerShape(8.dp), action = { - TextButton(onClick = { snackMsg = null }) { - Text("OK") - } + ExpressiveTextButton(onClick = { snackMsg = null }) { Text("OK") } }, ) { Text(snackMsg!!) } } }, floatingActionButton = { if (dirty && !saving && !maintenanceMode) { - FloatingActionButton( + ExpressiveFloatingActionButton( onClick = { - val opts = options ?: return@FloatingActionButton + val opts = options ?: return@ExpressiveFloatingActionButton saving = true scope.launch { try { @@ -94,9 +99,9 @@ fun SettingsTab(client: MaimaiClient, cookie: String, maintenanceMode: Boolean = saving = false } }, - containerColor = PrimaryBlue, + containerColor = MaterialTheme.colorScheme.primary, ) { - Icon(Icons.Default.Check, contentDescription = "Save", tint = CardWhite) + Icon(Icons.Default.Check, contentDescription = "Save", tint = MaterialTheme.colorScheme.onPrimary) } } }, @@ -115,15 +120,14 @@ fun SettingsTab(client: MaimaiClient, cookie: String, maintenanceMode: Boolean = val opts = options!! if (saving) { - LinearProgressIndicator(modifier = Modifier.fillMaxWidth(), color = PrimaryBlue) + ExpressiveLinearProgressIndicator(modifier = Modifier.fillMaxWidth(), color = MaterialTheme.colorScheme.primary) } // Friend Registration Skip if (friendSkip != null) { - Card( + ExpressiveGlassCard( modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 6.dp), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), + cornerRadius = 26.dp, ) { Row( modifier = Modifier.fillMaxWidth().padding(12.dp), @@ -131,11 +135,11 @@ fun SettingsTab(client: MaimaiClient, cookie: String, maintenanceMode: Boolean = verticalAlignment = Alignment.CenterVertically, ) { Column(modifier = Modifier.weight(1f)) { - Text("Friend Registration Skip", fontSize = 13.sp, fontWeight = FontWeight.Medium, color = DarkText) - Text("Skip friend registration after songs", fontSize = 11.sp, color = MutedText) + Text("Friend Registration Skip", fontSize = 13.sp, fontWeight = FontWeight.Medium, color = MaterialTheme.colorScheme.onSurface) + Text("Skip friend registration after songs", fontSize = 11.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } var skipState by remember { mutableStateOf(friendSkip!!.skip) } - Switch( + ExpressiveSwitch( checked = skipState, enabled = !maintenanceMode, onCheckedChange = { newValue -> @@ -157,7 +161,6 @@ fun SettingsTab(client: MaimaiClient, cookie: String, maintenanceMode: Boolean = saving = false } }, - colors = SwitchDefaults.colors(checkedTrackColor = PrimaryBlue), ) } } @@ -199,21 +202,20 @@ private fun OptionSection( ) { if (fields.isEmpty()) return - Card( + ExpressiveGlassCard( modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 6.dp), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = CardWhite), + cornerRadius = 28.dp, ) { Column(modifier = Modifier.padding(12.dp)) { - Text(title, fontSize = 16.sp, fontWeight = FontWeight.Bold, color = DarkText) + Text(title, fontSize = 16.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) Spacer(Modifier.height(8.dp)) fields.forEach { field -> OptionRow(field, disabled, onChanged) if (field != fields.last()) { - HorizontalDivider( + ExpressiveHorizontalDivider( modifier = Modifier.padding(vertical = 4.dp), - color = MutedText.copy(alpha = 0.12f), + color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.6f), ) } } @@ -222,7 +224,7 @@ private fun OptionSection( } /** Fields that should always be split buttons regardless of option count. */ -private val FORCE_SPLIT_BUTTON = setOf("mirrorMode", "tapDesign", "holdDesign", "slideDesign") +private val FORCE_SPLIT_BUTTON = setOf("mirrorMode", "holdDesign", "slideDesign") /** Icon overrides for specific option indices within a field. */ private val OPTION_ICONS: Map> = mapOf( @@ -263,16 +265,29 @@ private fun OptionRow( cleaned.name in FORCE_SPLIT_BUTTON -> { val icons = OPTION_ICONS[cleaned.name] - if (cleaned.name == "tapDesign") { - TapDesignSplitRow(cleaned, disabled, onChanged) - } else { - SplitButtonRow(cleaned, disabled, onChanged, icons) - } + val label = OPTION_LABELS[cleaned.name] ?: cleaned.name + SplitButtonRow( + label = label, + options = cleaned.options, + selected = cleaned.selected, + disabled = disabled, + icons = icons, + onSelect = { onChanged(cleaned.name, it) }, + ) } isSliderField(cleaned) -> SliderRow(cleaned, disabled, onChanged) - cleaned.options.size in 2..3 -> SplitButtonRow(cleaned, disabled, onChanged) + cleaned.options.size in 2..3 -> { + val label = OPTION_LABELS[cleaned.name] ?: cleaned.name + SplitButtonRow( + label = label, + options = cleaned.options, + selected = cleaned.selected, + disabled = disabled, + onSelect = { onChanged(cleaned.name, it) }, + ) + } else -> DropdownRow(cleaned, disabled, onChanged) } @@ -295,50 +310,27 @@ private fun SliderRow( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween, ) { - Text(label, fontSize = 13.sp, fontWeight = FontWeight.Medium, color = DarkText) + Text(label, fontSize = 13.sp, fontWeight = FontWeight.Medium, color = MaterialTheme.colorScheme.onSurface) Text( labels[selectedIdx], fontSize = 13.sp, - color = PrimaryBlue, + color = MaterialTheme.colorScheme.primary, fontWeight = FontWeight.SemiBold, ) } - Slider( + ExpressiveSlider( value = selectedIdx.toFloat(), onValueChange = { raw -> - if (disabled) return@Slider + if (disabled) return@ExpressiveSlider val i = (raw + 0.5f).toInt().coerceIn(0, stepCount) onChanged(field.name, field.options[i].first) }, valueRange = 0f..stepCount.toFloat(), enabled = !disabled, - colors = SliderDefaults.colors( - thumbColor = PrimaryBlue, - activeTrackColor = PrimaryBlue, - inactiveTrackColor = MutedText.copy(alpha = 0.2f), - ), ) } } -@Composable -private fun SplitButtonRow( - field: OptionField, - disabled: Boolean = false, - onChanged: (fieldName: String, newValue: String) -> Unit, - icons: Map? = null, -) { - val label = OPTION_LABELS[field.name] ?: field.name - SharedSplitButtonRow( - label = label, - options = field.options, - selected = field.selected, - disabled = disabled, - icons = icons, - onSelect = { onChanged(field.name, it) }, - ) -} - @OptIn(ExperimentalMaterial3Api::class) @Composable private fun DropdownRow( @@ -358,7 +350,7 @@ private fun DropdownRow( label, fontSize = 13.sp, fontWeight = FontWeight.Medium, - color = DarkText, + color = MaterialTheme.colorScheme.onSurface, modifier = Modifier.weight(1f), ) @@ -372,32 +364,35 @@ private fun DropdownRow( Text( selectedLabel, fontSize = 13.sp, - color = if (disabled) MutedText else PrimaryBlue, + color = if (disabled) MaterialTheme.colorScheme.onSurfaceVariant else MaterialTheme.colorScheme.primary, fontWeight = FontWeight.SemiBold, ) Icon( Icons.Default.ExpandMore, contentDescription = null, modifier = Modifier.size(18.dp), - tint = if (disabled) MutedText else PrimaryBlue, + tint = if (disabled) MaterialTheme.colorScheme.onSurfaceVariant else MaterialTheme.colorScheme.primary, ) } - DropdownMenu( + ExpressiveDropdownMenu( expanded = expanded && !disabled, onDismissRequest = { expanded = false }, - containerColor = CardWhite, ) { - field.options.forEach { (value, optLabel) -> - DropdownMenuItem( + field.options.forEachIndexed { index, (value, optLabel) -> + val selected = value == field.selected + ExpressiveDropdownMenuItem( + selected = selected, text = { Text( optLabel, fontSize = 13.sp, - fontWeight = if (value == field.selected) FontWeight.Bold else FontWeight.Normal, - color = if (value == field.selected) PrimaryBlue else DarkText, + fontWeight = if (selected) FontWeight.Bold else FontWeight.Normal, ) }, + leadingIcon = if (selected) ({ Icon(Icons.Default.Check, contentDescription = null) }) else null, + index = index, + lastIndex = field.options.lastIndex, onClick = { onChanged(field.name, value) expanded = false @@ -439,7 +434,7 @@ private fun DispJudgeRow( label, fontSize = 13.sp, fontWeight = FontWeight.Medium, - color = DarkText, + color = MaterialTheme.colorScheme.onSurface, modifier = Modifier.weight(1f), ) @@ -453,47 +448,53 @@ private fun DispJudgeRow( Text( selectedLabel, fontSize = 13.sp, - color = if (disabled) MutedText else PrimaryBlue, + color = if (disabled) MaterialTheme.colorScheme.onSurfaceVariant else MaterialTheme.colorScheme.primary, fontWeight = FontWeight.SemiBold, ) Icon( Icons.Default.ExpandMore, contentDescription = null, modifier = Modifier.size(18.dp), - tint = if (disabled) MutedText else PrimaryBlue, + tint = if (disabled) MaterialTheme.colorScheme.onSurfaceVariant else MaterialTheme.colorScheme.primary, ) } - DropdownMenu( + ExpressiveDropdownMenu( expanded = expanded && !disabled, onDismissRequest = { expanded = false }, - containerColor = CardWhite, ) { // DISPLAY OFF option - DropdownMenuItem( + ExpressiveDropdownMenuItem( + selected = field.selected == "-1", text = { Text( "DISPLAY OFF", fontSize = 13.sp, fontWeight = if (field.selected == "-1") FontWeight.Bold else FontWeight.Normal, - color = if (field.selected == "-1") PrimaryBlue else DarkText, ) }, + leadingIcon = if (field.selected == "-1") ({ Icon(Icons.Default.Check, contentDescription = null) }) else null, + index = 0, + lastIndex = field.options.size, onClick = { onChanged(field.name, "-1") expanded = false }, ) - field.options.forEach { (value, optLabel) -> - DropdownMenuItem( + field.options.forEachIndexed { index, (value, optLabel) -> + val selected = value == field.selected + ExpressiveDropdownMenuItem( + selected = selected, text = { Text( optLabel, fontSize = 13.sp, - fontWeight = if (value == field.selected) FontWeight.Bold else FontWeight.Normal, - color = if (value == field.selected) PrimaryBlue else DarkText, + fontWeight = if (selected) FontWeight.Bold else FontWeight.Normal, ) }, + leadingIcon = if (selected) ({ Icon(Icons.Default.Check, contentDescription = null) }) else null, + index = index + 1, + lastIndex = field.options.size, onClick = { onChanged(field.name, value) expanded = false @@ -513,74 +514,9 @@ private fun DispJudgeRow( contentDescription = "Judge display preview", modifier = Modifier .fillMaxWidth() - .clip(RoundedCornerShape(6.dp)), + .clip(MaterialTheme.shapes.large), contentScale = ContentScale.FillWidth, ) } } } - -/** Tap Design split button — last option shows tapkun icon instead of text. */ -@Composable -private fun TapDesignSplitRow( - field: OptionField, - disabled: Boolean = false, - onChanged: (fieldName: String, newValue: String) -> Unit, -) { - val label = OPTION_LABELS[field.name] ?: field.name - val tapkunBitmap = remember { mutableStateOf(null) } - LaunchedEffect(Unit) { - tapkunBitmap.value = MaimaiAssets.load("tapkun.png") - } - - Row( - modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - label, - fontSize = 13.sp, - fontWeight = FontWeight.Medium, - color = DarkText, - modifier = Modifier.weight(1f), - ) - - Row( - horizontalArrangement = Arrangement.spacedBy(0.dp), - modifier = Modifier.clip(RoundedCornerShape(6.dp)), - ) { - field.options.forEachIndexed { i, (value, optLabel) -> - val selected = value == field.selected - val bgColor = if (selected) PrimaryBlue else MutedText.copy(alpha = 0.1f) - val textColor = if (selected) CardWhite else DarkText - - Box( - modifier = Modifier - .defaultMinSize(minWidth = 44.dp, minHeight = 38.dp) - .background(if (disabled) bgColor.copy(alpha = 0.4f) else bgColor) - .clickable(enabled = !disabled) { onChanged(field.name, value) } - .padding(horizontal = 10.dp, vertical = 6.dp), - contentAlignment = Alignment.Center, - ) { - // Last option (index 4) = Tapkun — show icon if loaded - if (i == 4 && tapkunBitmap.value != null) { - Image( - bitmap = tapkunBitmap.value!!, - contentDescription = "Tapkun", - modifier = Modifier.size(24.dp), - contentScale = ContentScale.Fit, - ) - } else { - Text( - optLabel, - fontSize = 12.sp, - fontWeight = if (selected) FontWeight.Bold else FontWeight.Medium, - color = textColor, - textAlign = TextAlign.Center, - ) - } - } - } - } - } -} diff --git a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/theme/Theme.kt b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/theme/Theme.kt index 7e8d421..a45c7af 100644 --- a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/theme/Theme.kt +++ b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/theme/Theme.kt @@ -1,25 +1,45 @@ package com.derakkuma.ui.theme -import androidx.compose.material3.MaterialTheme +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.MaterialExpressiveTheme +import androidx.compose.material3.MotionScheme +import androidx.compose.material3.Shapes import androidx.compose.material3.Typography import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import derakkuma.composeapp.generated.resources.Res import derakkuma.composeapp.generated.resources.segamaru_db import org.jetbrains.compose.resources.ExperimentalResourceApi import org.jetbrains.compose.resources.Font val MaimaiBlue = Color(0xFF51BCF3) -val CardWhite = Color(0xE0FFFFFF) +val CardWhite = Color(0xF2FFFFFF) val DarkText = Color(0xFF111827) val MutedText = Color(0xFF6B7280) val AccentPink = Color(0xFFBE185D) -val PrimaryBlue = Color(0xFF2563EB) +val PrimaryBlue = Color(0xFF1769F5) val DangerRed = Color(0xFFDC2626) +// Material 3 expressive palette: saturated, game-like colors with soft candy surfaces. +val ExpressiveSky = Color(0xFF43BDF4) +val ExpressiveOcean = Color(0xFF1769F5) +val ExpressiveBubblegum = Color(0xFFFF5FA2) +val ExpressiveLemon = Color(0xFFFFD84D) +val ExpressiveLime = Color(0xFF7CD82E) +val ExpressiveGrape = Color(0xFF865DFF) +val ExpressivePeach = Color(0xFFFF8D5C) +val ExpressiveMint = Color(0xFFBFF7E2) +val ExpressiveSurface = Color(0xFFF8FCFF) +val ExpressiveSurfaceVariant = Color(0xFFE7F4FF) +val ExpressiveOutline = Color(0x332563EB) + // maimai difficulty colors val DiffBasic = Color(0xFF22BB5B) val DiffAdvanced = Color(0xFFF9A825) @@ -27,6 +47,11 @@ val DiffExpert = Color(0xFFFF5252) val DiffMaster = Color(0xFFAB47BC) val DiffRemaster = Color(0xFFE0C3FF) +// Ranking medal colors +val MedalGold = Color(0xFFFFD700) +val MedalSilver = Color(0xFFC0C0C0) +val MedalBronze = Color(0xFFCD7F32) + fun difficultyColor(difficulty: String): Color = when (difficulty.lowercase()) { "basic" -> DiffBasic "advanced" -> DiffAdvanced @@ -37,11 +62,36 @@ fun difficultyColor(difficulty: String): Color = when (difficulty.lowercase()) { } private val colorScheme = lightColorScheme( - primary = PrimaryBlue, - background = MaimaiBlue, - surface = CardWhite, + primary = ExpressiveOcean, + onPrimary = Color.White, + primaryContainer = Color(0xFFD7E7FF), + onPrimaryContainer = Color(0xFF06285E), + secondary = ExpressiveBubblegum, + onSecondary = Color.White, + secondaryContainer = Color(0xFFFFD8E6), + onSecondaryContainer = Color(0xFF5A0C2C), + tertiary = ExpressiveGrape, + onTertiary = Color.White, + tertiaryContainer = Color(0xFFE9DDFF), + onTertiaryContainer = Color(0xFF27105C), + background = ExpressiveSky, + onBackground = Color.White, + surface = ExpressiveSurface, onSurface = DarkText, + surfaceVariant = ExpressiveSurfaceVariant, + onSurfaceVariant = MutedText, + outline = ExpressiveOutline, error = DangerRed, + errorContainer = Color(0xFFFFDAD6), + onErrorContainer = Color(0xFF410002), +) + +val ExpressiveShapes = Shapes( + extraSmall = RoundedCornerShape(12.dp), + small = RoundedCornerShape(16.dp), + medium = RoundedCornerShape(24.dp), + large = RoundedCornerShape(32.dp), + extraLarge = RoundedCornerShape(40.dp), ) @OptIn(ExperimentalResourceApi::class) @@ -56,30 +106,42 @@ fun SegaMaruFontFamily() = FontFamily( @Composable fun SegaMaruTypography() = Typography().run { val fontFamily = SegaMaruFontFamily() + fun TextStyle.withDerakkuma( + weight: FontWeight = FontWeight.Normal, + tracking: Float = 0f, + ) = copy( + fontFamily = fontFamily, + fontWeight = weight, + letterSpacing = tracking.sp, + ) + copy( - displayLarge = displayLarge.copy(fontFamily = fontFamily), - displayMedium = displayMedium.copy(fontFamily = fontFamily), - displaySmall = displaySmall.copy(fontFamily = fontFamily), - headlineLarge = headlineLarge.copy(fontFamily = fontFamily), - headlineMedium = headlineMedium.copy(fontFamily = fontFamily), - headlineSmall = headlineSmall.copy(fontFamily = fontFamily), - titleLarge = titleLarge.copy(fontFamily = fontFamily), - titleMedium = titleMedium.copy(fontFamily = fontFamily), - titleSmall = titleSmall.copy(fontFamily = fontFamily), - bodyLarge = bodyLarge.copy(fontFamily = fontFamily), - bodyMedium = bodyMedium.copy(fontFamily = fontFamily), - bodySmall = bodySmall.copy(fontFamily = fontFamily), - labelLarge = labelLarge.copy(fontFamily = fontFamily), - labelMedium = labelMedium.copy(fontFamily = fontFamily), - labelSmall = labelSmall.copy(fontFamily = fontFamily), + displayLarge = displayLarge.withDerakkuma(FontWeight.ExtraBold, -0.8f), + displayMedium = displayMedium.withDerakkuma(FontWeight.ExtraBold, -0.6f), + displaySmall = displaySmall.withDerakkuma(FontWeight.ExtraBold, -0.4f), + headlineLarge = headlineLarge.withDerakkuma(FontWeight.ExtraBold, -0.3f), + headlineMedium = headlineMedium.withDerakkuma(FontWeight.ExtraBold, -0.2f), + headlineSmall = headlineSmall.withDerakkuma(FontWeight.Bold, -0.1f), + titleLarge = titleLarge.withDerakkuma(FontWeight.ExtraBold, -0.1f), + titleMedium = titleMedium.withDerakkuma(FontWeight.Bold), + titleSmall = titleSmall.withDerakkuma(FontWeight.SemiBold), + bodyLarge = bodyLarge.withDerakkuma(FontWeight.Normal), + bodyMedium = bodyMedium.withDerakkuma(FontWeight.Normal), + bodySmall = bodySmall.withDerakkuma(FontWeight.Normal), + labelLarge = labelLarge.withDerakkuma(FontWeight.ExtraBold, 0.2f), + labelMedium = labelMedium.withDerakkuma(FontWeight.Bold, 0.2f), + labelSmall = labelSmall.withDerakkuma(FontWeight.SemiBold, 0.2f), ) } +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun DerakkumaTheme(content: @Composable () -> Unit) { - MaterialTheme( + MaterialExpressiveTheme( colorScheme = colorScheme, + motionScheme = MotionScheme.expressive(), typography = SegaMaruTypography(), + shapes = ExpressiveShapes, content = content, ) } diff --git a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/util/CopyButton.kt b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/util/CopyButton.kt index 68291c0..7308905 100644 --- a/composeApp/src/commonMain/kotlin/com/derakkuma/ui/util/CopyButton.kt +++ b/composeApp/src/commonMain/kotlin/com/derakkuma/ui/util/CopyButton.kt @@ -3,12 +3,12 @@ package com.derakkuma.ui.util import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ContentCopy import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalClipboard -import com.derakkuma.ui.theme.PrimaryBlue +import com.derakkuma.ui.components.ExpressiveFilledTonalIconButton import kotlinx.coroutines.launch @Composable @@ -16,7 +16,7 @@ fun CopyButton(text: String, modifier: Modifier = Modifier) { val clipboard = LocalClipboard.current val scope = rememberCoroutineScope() - IconButton( + ExpressiveFilledTonalIconButton( onClick = { scope.launch { text.copyToClipboard(clipboard) @@ -27,7 +27,7 @@ fun CopyButton(text: String, modifier: Modifier = Modifier) { Icon( imageVector = Icons.Default.ContentCopy, contentDescription = "Copy", - tint = PrimaryBlue, + tint = MaterialTheme.colorScheme.primary, ) } } diff --git a/composeApp/src/iosMain/kotlin/com/derrakuma/appicon/AppIcon.ios.kt b/composeApp/src/iosMain/kotlin/com/derrakuma/appicon/AppIcon.ios.kt index 9f7935e..9d29a5c 100644 --- a/composeApp/src/iosMain/kotlin/com/derrakuma/appicon/AppIcon.ios.kt +++ b/composeApp/src/iosMain/kotlin/com/derrakuma/appicon/AppIcon.ios.kt @@ -19,12 +19,11 @@ actual fun platformUsesIosAppIconPreviews(): Boolean = true @Composable actual fun platformMonochromeAppIconColors(): Pair = Color(0xFFEADDFF) to Color(0xFF4F378B) -actual suspend fun loadAppIconPreviewAsset(path: String): ImageBitmap? = - runCatching { - val name = path.substringAfterLast('/').substringBeforeLast(".") - val image = UIImage.imageNamed(name) ?: return@runCatching null - UIImagePNGRepresentation(image)?.toByteArray()?.decodeToImageBitmap() - }.getOrNull() +actual suspend fun loadAppIconPreviewAsset(path: String): ImageBitmap? = runCatching { + val name = path.substringAfterLast('/').substringBeforeLast(".") + val image = UIImage.imageNamed(name) ?: return@runCatching null + UIImagePNGRepresentation(image)?.toByteArray()?.decodeToImageBitmap() +}.getOrNull() actual fun setAppIcon(option: AppIconOption): Boolean { setAlternateAppIconName(option.iosName) diff --git a/gradle.properties b/gradle.properties index e35dc0b..fe5adc5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,3 +2,4 @@ org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 kotlin.code.style=official android.useAndroidX=true android.nonTransitiveRClass=true +org.gradle.configuration-cache=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6b22f1a..814de83 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,13 +1,15 @@ [versions] kotlin = "2.3.20" compose-multiplatform = "1.11.0" -agp = "8.9.3" +agp = "9.1.1" ktor = "3.5.0" kotlinx-serialization = "1.11.0" kotlinx-coroutines = "1.10.2" kotlinx-datetime = "0.7.1" androidx-activity = "1.10.1" androidx-lifecycle = "2.9.0" +androidx-browser = "1.8.0" +androidx-core = "1.17.0" navigation-compose = "2.9.0" datastore = "1.1.7" sqldelight = "2.1.0" @@ -34,6 +36,8 @@ kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-c kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" } androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" } +androidx-browser = { module = "androidx.browser:browser", version.ref = "androidx-browser" } +androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidx-core" } androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" } androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastore" } navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "navigation-compose" } @@ -65,4 +69,5 @@ kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", versi compose-multiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } android-application = { id = "com.android.application", version.ref = "agp" } +android-multiplatform-library = { id = "com.android.kotlin.multiplatform.library", version.ref = "agp" } sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 680d395..9354476 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip networkTimeout=10000 retries=0 retryBackOffMs=500 diff --git a/settings.gradle.kts b/settings.gradle.kts index ba1c48b..83654b1 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -17,6 +17,7 @@ dependencyResolutionManagement { } include(":composeApp") +include(":androidApp") includeBuild("third_party/Compose-HeatMap") { dependencySubstitution { diff --git a/third_party/Compose-HeatMap b/third_party/Compose-HeatMap index 00d897f..a411cb7 160000 --- a/third_party/Compose-HeatMap +++ b/third_party/Compose-HeatMap @@ -1 +1 @@ -Subproject commit 00d897f216b87744628dea65277a6dcd9da280a3 +Subproject commit a411cb76cdcc7a487ed500add306615d6a3baec1 diff --git a/third_party/kmp-maps b/third_party/kmp-maps index ef38bab..4d13123 160000 --- a/third_party/kmp-maps +++ b/third_party/kmp-maps @@ -1 +1 @@ -Subproject commit ef38babb6203897dc9ba794e35591cdca2adfdab +Subproject commit 4d131232c1c5d83ba96032e87ddf21e99d7beecb