diff --git a/.editorconfig b/.editorconfig index e87cee2..e4eeed9 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,22 +1,6 @@ -# https://pinterest.github.io/ktlint/latest/rules/standard - root = true -[*.{kt, kts}] -ktlint_code_style = ktlint_official -indent_size = 4 -indent_style = space -ij_kotlin_allow_trailing_comma = false -ij_kotlin_allow_trailing_comma_on_call_site = false - -# enabled, disabled -ktlint_standard = enabled -ktlint_experimental = disabled -ktlint_custom-rule-set = disabled - -max_line_length = off -insert_final_newline = false - -# ktlint_standard_multiline-expression-wrapping = disabled -# ktlint_standard_import-ordering = disabled -# ktlint_standard_trailing-comma-on-declaration-site = disabled +[*.{kt,kts}] +ij_kotlin_allow_trailing_comma = true +ij_kotlin_allow_trailing_comma_on_call_site = true +ktlint_function_naming_ignore_when_annotated_with = Composable, Test diff --git a/.github/workflows/Build.yaml b/.github/workflows/Build.yaml index a0b3c36..cb6c4ed 100644 --- a/.github/workflows/Build.yaml +++ b/.github/workflows/Build.yaml @@ -17,16 +17,16 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Validate Gradle Wrapper - uses: gradle/wrapper-validation-action@v1 + uses: gradle/wrapper-validation-action@v3 - name: Copy CI gradle.properties run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: 17 @@ -46,29 +46,14 @@ jobs: key: gradle-${{ hashFiles('checksum.txt') }} - name: Setup Gradle - uses: gradle/gradle-build-action@v2 + uses: gradle/gradle-build-action@v3 - - name: Check spotless + - name: Check Spotless run: ./gradlew spotlessCheck --init-script gradle/init.gradle.kts --no-configuration-cache --stacktrace - - name: Check lint - run: ./gradlew lintDebug --stacktrace - - # - name: Build all build type and flavor permutations - # run: ./gradlew assemble --stacktrace - - # - name: Run local tests - # run: ./gradlew testDebug testProdDebug --stacktrace - - # - name: Upload build outputs (APKs) - # uses: actions/upload-artifact@v3 - # with: - # name: build-outputs - # path: app/build/outputs - - name: Upload build reports if: always() uses: actions/upload-artifact@v3 with: name: build-reports - path: app/build/reports + path: app/build/reports \ No newline at end of file diff --git a/README.md b/README.md index eb1dc0a..55dea7b 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@

- +

@@ -28,14 +28,18 @@ - MVVM pattern - Kotlin Coroutines & Flows - Material Design 3 + - [Material Theme Builder](https://material-foundation.github.io/material-theme-builder/) - Single Activity - StaggeredVerticalGrid - [Gradle Version Catalog](https://docs.gradle.org/7.4/userguide/platforms.html) - [Retrofit2](https://github.com/square/retrofit) - [Coil-Compose](https://coil-kt.github.io/coil/compose) - [Timber](https://github.com/JakeWharton/timber) +- [Haze](https://github.com/chrisbanes/haze) +- [sharedElement](https://developer.android.com/guide/fragments/animate#shared) ## Multi Module + ``` ├── app ├── core diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f080c2b..181db15 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -31,7 +31,7 @@ android { isMinifyEnabled = true proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" + "proguard-rules.pro", ) signingConfig = signingConfigs.getByName("debug") } @@ -48,4 +48,5 @@ dependencies { implementation(libs.androidx.startup) implementation(libs.androidx.compose.material3) implementation(libs.timber) + implementation(libs.coil.kt) } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0820cb5..b8ba5ab 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + + diff --git a/app/src/main/java/io/github/shinhyo/brba/initializer/CoilInitializer.kt b/app/src/main/java/io/github/shinhyo/brba/initializer/CoilInitializer.kt new file mode 100644 index 0000000..b19dc89 --- /dev/null +++ b/app/src/main/java/io/github/shinhyo/brba/initializer/CoilInitializer.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2024 shinhyo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.github.shinhyo.brba.initializer + +import android.content.Context +import androidx.startup.Initializer +import coil.Coil +import coil.ImageLoader +import coil.disk.DiskCache +import coil.memory.MemoryCache + +class CoilInitializer : Initializer { + + override fun create(context: Context) { + Coil.setImageLoader( + ImageLoader.Builder(context) + .memoryCache { + MemoryCache.Builder(context) + .maxSizePercent(0.25) + .build() + } + .crossfade(true) +// .logger(if (BuildConfig.DEBUG) DebugLogger() else null) + .respectCacheHeaders(false) + .diskCache { + DiskCache.Builder() + .directory(context.cacheDir.resolve("image_cache")) + .maxSizePercent(0.10) + .build() + } + .build(), + ) + } + + override fun dependencies(): MutableList>> = mutableListOf() +} \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 55344e5..3ea04e7 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,3 +1,2 @@ - - \ No newline at end of file + diff --git a/build-logic/convention/build.gradle.kts b/build-logic/convention/build.gradle.kts index 4b49c5f..011b12c 100644 --- a/build-logic/convention/build.gradle.kts +++ b/build-logic/convention/build.gradle.kts @@ -22,5 +22,4 @@ dependencies { compileOnly(libs.android.tools.common) compileOnly(libs.kotlin.gradlePlugin) compileOnly(libs.ksp.gradlePlugin) - compileOnly(libs.ktlint.gradlePlugin) -} \ No newline at end of file +} diff --git a/build-logic/convention/src/main/kotlin/brba.android.application.gradle.kts b/build-logic/convention/src/main/kotlin/brba.android.application.gradle.kts index 75192a4..fad027e 100644 --- a/build-logic/convention/src/main/kotlin/brba.android.application.gradle.kts +++ b/build-logic/convention/src/main/kotlin/brba.android.application.gradle.kts @@ -2,7 +2,6 @@ import com.android.build.api.dsl.ApplicationExtension import io.github.shinhyo.brba.buildlogic.configureAndroidCompose import io.github.shinhyo.brba.buildlogic.configureHiltAndroid import io.github.shinhyo.brba.buildlogic.configureKotlinAndroid -import io.github.shinhyo.brba.buildlogic.configureKtlintAndroid import io.github.shinhyo.brba.buildlogic.findVersion with(pluginManager) { @@ -16,4 +15,3 @@ extensions.configure { configureKotlinAndroid() configureAndroidCompose() configureHiltAndroid() -configureKtlintAndroid() diff --git a/build-logic/convention/src/main/kotlin/brba.android.feature.gradle.kts b/build-logic/convention/src/main/kotlin/brba.android.feature.gradle.kts index a6b49d1..77b210e 100644 --- a/build-logic/convention/src/main/kotlin/brba.android.feature.gradle.kts +++ b/build-logic/convention/src/main/kotlin/brba.android.feature.gradle.kts @@ -15,9 +15,9 @@ androidExtension.apply { add("implementation", findLibrary("coil.kt.compose")) - add("implementation", findLibrary("androidx.constraintlayout.compose")) add("implementation", findLibrary("androidx.compose.material3")) add("implementation", findLibrary("androidx.compose.material.iconsExtended")) + add("implementation", findLibrary("androidx.compose.animation")) add("implementation", findLibrary("haze")) add("implementation", findLibrary("androidx.navigation.compose")) diff --git a/build-logic/convention/src/main/kotlin/brba.android.library.gradle.kts b/build-logic/convention/src/main/kotlin/brba.android.library.gradle.kts index b93cf1b..421127c 100644 --- a/build-logic/convention/src/main/kotlin/brba.android.library.gradle.kts +++ b/build-logic/convention/src/main/kotlin/brba.android.library.gradle.kts @@ -1,7 +1,6 @@ import io.github.shinhyo.brba.buildlogic.androidExtension import io.github.shinhyo.brba.buildlogic.configureHiltAndroid import io.github.shinhyo.brba.buildlogic.configureKotlinAndroid -import io.github.shinhyo.brba.buildlogic.configureKtlintAndroid import io.github.shinhyo.brba.buildlogic.findLibrary with(pluginManager) { @@ -16,4 +15,3 @@ androidExtension.apply { configureKotlinAndroid() configureHiltAndroid() -configureKtlintAndroid() \ No newline at end of file diff --git a/build-logic/convention/src/main/kotlin/io/github/shinhyo/brba/buildlogic/KotlinAndroid.kt b/build-logic/convention/src/main/kotlin/io/github/shinhyo/brba/buildlogic/KotlinAndroid.kt index d360118..b7f3dd2 100644 --- a/build-logic/convention/src/main/kotlin/io/github/shinhyo/brba/buildlogic/KotlinAndroid.kt +++ b/build-logic/convention/src/main/kotlin/io/github/shinhyo/brba/buildlogic/KotlinAndroid.kt @@ -34,13 +34,15 @@ private fun Project.configureKotlin() { allWarningsAsErrors = properties["warningsAsErrors"] as? Boolean ?: false compilerOptions.freeCompilerArgs.addAll( "-P", - "plugin:androidx.compose.compiler.plugins.kotlin:experimentalStrongSkipping=true", + "plugin:androidx.compose.compiler.plugins.kotlin:strongSkipping=true", ) freeCompilerArgs = freeCompilerArgs + listOf( + "-Xcontext-receivers", "-opt-in=kotlin.RequiresOptIn", "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api", "-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi", + "-opt-in=androidx.compose.animation.ExperimentalSharedTransitionApi", ) } } diff --git a/build-logic/convention/src/main/kotlin/io/github/shinhyo/brba/buildlogic/KtlintAndroid.kt b/build-logic/convention/src/main/kotlin/io/github/shinhyo/brba/buildlogic/KtlintAndroid.kt deleted file mode 100644 index ae9e6b3..0000000 --- a/build-logic/convention/src/main/kotlin/io/github/shinhyo/brba/buildlogic/KtlintAndroid.kt +++ /dev/null @@ -1,23 +0,0 @@ -package io.github.shinhyo.brba.buildlogic - -import org.gradle.api.Project -import org.gradle.kotlin.dsl.configure - -internal fun Project.configureKtlintAndroid() { - with(pluginManager) { - apply("org.jlleitschuh.gradle.ktlint") - apply("org.jlleitschuh.gradle.ktlint-idea") - } - - // https://github.com/JLLeitschuh/ktlint-gradle#configuration - configure { -// version.set("1.0.1") - debug.set(true) - verbose.set(true) - android.set(true) - outputToConsole.set(true) - outputColorName.set("RED") - ignoreFailures.set(false) - enableExperimentalRules.set(false) - } -} diff --git a/build.gradle.kts b/build.gradle.kts index ae156fd..95e184d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,7 +11,6 @@ plugins { alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.hilt) apply false alias(libs.plugins.ksp) apply false - alias(libs.plugins.jlleitschuh.gradle.ktlint) apply false } apply(from = File("gradle/projectDependencyGraph.gradle")) diff --git a/core/common/src/main/java/io/github/shinhyo/brba/core/common/di/BrbaDispatcher.kt b/core/common/src/main/java/io/github/shinhyo/brba/core/common/di/BrbaDispatcher.kt index 0611347..7ba137b 100644 --- a/core/common/src/main/java/io/github/shinhyo/brba/core/common/di/BrbaDispatcher.kt +++ b/core/common/src/main/java/io/github/shinhyo/brba/core/common/di/BrbaDispatcher.kt @@ -22,5 +22,6 @@ import javax.inject.Qualifier annotation class Dispatcher(val dispatcher: BrbaDispatcher) enum class BrbaDispatcher { - IO, MAIN + IO, + MAIN, } \ No newline at end of file diff --git a/core/common/src/main/java/io/github/shinhyo/brba/core/common/result/Result.kt b/core/common/src/main/java/io/github/shinhyo/brba/core/common/result/Result.kt index 1288d0a..f78b7c5 100644 --- a/core/common/src/main/java/io/github/shinhyo/brba/core/common/result/Result.kt +++ b/core/common/src/main/java/io/github/shinhyo/brba/core/common/result/Result.kt @@ -24,7 +24,7 @@ import kotlinx.coroutines.flow.onStart sealed interface Result { data object Loading : Result data class Success(val data: T) : Result - data class Error(val exception: Throwable? = null) : Result + data class Error(val exception: Throwable) : Result } val Result<*>.succeeded @@ -37,4 +37,4 @@ fun Result.successOr(fallback: T): T { fun Flow.asResult(): Flow> = this .map> { Success(it) } .onStart { emit(Result.Loading) } - .catch { emit(Result.Error(it)) } \ No newline at end of file + .catch { e -> emit(Result.Error(e)) } \ No newline at end of file diff --git a/core/data/src/main/java/io/github/shinhyo/brba/core/data/di/RepositoryModule.kt b/core/data/src/main/java/io/github/shinhyo/brba/core/data/di/RepositoryModule.kt index 2658444..b8ec3e2 100644 --- a/core/data/src/main/java/io/github/shinhyo/brba/core/data/di/RepositoryModule.kt +++ b/core/data/src/main/java/io/github/shinhyo/brba/core/data/di/RepositoryModule.kt @@ -32,12 +32,12 @@ abstract class RepositoryModule { @Binds @Singleton abstract fun bindCharactersRepository( - repo: CharactersRepositoryImpl + repo: CharactersRepositoryImpl, ): CharactersRepository @Binds @Singleton abstract fun bindDeviceRepository( - repo: DeviceRepositoryImpl + repo: DeviceRepositoryImpl, ): DeviceRepository } \ No newline at end of file diff --git a/core/data/src/main/java/io/github/shinhyo/brba/core/data/model/Character.kt b/core/data/src/main/java/io/github/shinhyo/brba/core/data/model/Character.kt index 802cb44..d3f2134 100644 --- a/core/data/src/main/java/io/github/shinhyo/brba/core/data/model/Character.kt +++ b/core/data/src/main/java/io/github/shinhyo/brba/core/data/model/Character.kt @@ -24,5 +24,5 @@ fun BrbaCharacter.asEntity() = CharacterEntity( charId = charId, name = name, img = img, - nickname = nickname + nickname = nickname, ) \ No newline at end of file diff --git a/core/data/src/main/java/io/github/shinhyo/brba/core/data/repository/CharactersRepositoryImpl.kt b/core/data/src/main/java/io/github/shinhyo/brba/core/data/repository/CharactersRepositoryImpl.kt index 24fdd33..f149cc2 100644 --- a/core/data/src/main/java/io/github/shinhyo/brba/core/data/repository/CharactersRepositoryImpl.kt +++ b/core/data/src/main/java/io/github/shinhyo/brba/core/data/repository/CharactersRepositoryImpl.kt @@ -24,25 +24,27 @@ import io.github.shinhyo.brba.core.model.BrbaCharacter import io.github.shinhyo.brba.core.network.NetworkDataSource import io.github.shinhyo.brba.core.network.model.CharacterResponse import io.github.shinhyo.brba.core.network.model.asExternalModel -import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map +import javax.inject.Inject open class CharactersRepositoryImpl @Inject constructor( private val api: NetworkDataSource, - private val dao: CharacterDao + private val dao: CharacterDao, ) : CharactersRepository { override fun getCharacterList(): Flow> = flow { emit(api.getCharacter()) } .map { it.map(CharacterResponse::asExternalModel) } - override fun getCharacterList(id: Long): Flow = flow { emit(api.getCharacter(id)) } - .map { it.first().asExternalModel() } + override fun getCharacterList(id: Long): Flow = + flow { emit(api.getCharacter(id)) } + .map { it.first().asExternalModel() } - override fun getDatabaseList(isAsc: Boolean): Flow> = dao.getCharacter(isAsc = isAsc) - .map { it.map(CharacterEntity::asExternalModel) } + override fun getDatabaseList(isAsc: Boolean): Flow> = + dao.getCharacter(isAsc = isAsc) + .map { it.map(CharacterEntity::asExternalModel) } override fun getDatabaseList(id: Long): Flow = dao.getCharacter(charId = id) .map { it?.asExternalModel() } diff --git a/core/data/src/main/java/io/github/shinhyo/brba/core/data/repository/DeviceRepositoryImpl.kt b/core/data/src/main/java/io/github/shinhyo/brba/core/data/repository/DeviceRepositoryImpl.kt index 7b86680..14db977 100644 --- a/core/data/src/main/java/io/github/shinhyo/brba/core/data/repository/DeviceRepositoryImpl.kt +++ b/core/data/src/main/java/io/github/shinhyo/brba/core/data/repository/DeviceRepositoryImpl.kt @@ -19,18 +19,18 @@ import io.github.shinhyo.brba.core.datastore.DeviceDataSource import io.github.shinhyo.brba.core.domain.repository.DeviceRepository import io.github.shinhyo.brba.core.model.BrbaDeviceData import io.github.shinhyo.brba.core.model.BrbaThemeMode -import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map +import javax.inject.Inject class DeviceRepositoryImpl @Inject constructor( - private val deviceDataSource: DeviceDataSource + private val deviceDataSource: DeviceDataSource, ) : DeviceRepository { override val deviceData: Flow = deviceDataSource.deviceDataFlow .map { BrbaDeviceData( - themeMode = it.themeMode + themeMode = it.themeMode, ) } diff --git a/core/database/src/main/java/io/github/shinhyo/brba/core/database/AppDatabase.kt b/core/database/src/main/java/io/github/shinhyo/brba/core/database/AppDatabase.kt index 115fb8b..383dba8 100644 --- a/core/database/src/main/java/io/github/shinhyo/brba/core/database/AppDatabase.kt +++ b/core/database/src/main/java/io/github/shinhyo/brba/core/database/AppDatabase.kt @@ -25,10 +25,10 @@ import io.github.shinhyo.brba.core.database.util.DateTypeConverter @Database( entities = [ - CharacterEntity::class + CharacterEntity::class, ], version = DB_VERSION, - exportSchema = false + exportSchema = false, ) @TypeConverters(DateTypeConverter::class) abstract class AppDatabase : RoomDatabase() { diff --git a/core/database/src/main/java/io/github/shinhyo/brba/core/database/dao/CharacterDao.kt b/core/database/src/main/java/io/github/shinhyo/brba/core/database/dao/CharacterDao.kt index 9bf83ab..72a415b 100644 --- a/core/database/src/main/java/io/github/shinhyo/brba/core/database/dao/CharacterDao.kt +++ b/core/database/src/main/java/io/github/shinhyo/brba/core/database/dao/CharacterDao.kt @@ -31,7 +31,7 @@ interface CharacterDao { @Query( """ SELECT * FROM ${CharacterEntity.TABLE_NAME} - """ + """, ) fun getAll(): Flow> @@ -40,7 +40,7 @@ interface CharacterDao { SELECT * FROM ${CharacterEntity.TABLE_NAME} t WHERE t.charId=:charId ORDER BY t.ctime - """ + """, ) fun getCharacter(charId: Long): Flow @@ -50,7 +50,7 @@ interface CharacterDao { WHERE t.favorite = 1 ORDER BY CASE WHEN :isAsc = 1 THEN t.ctime END ASC, CASE WHEN :isAsc = 0 THEN t.ctime END DESC - """ + """, ) fun getCharacter(isAsc: Boolean = true): Flow> } \ No newline at end of file diff --git a/core/database/src/main/java/io/github/shinhyo/brba/core/database/di/DaoModule.kt b/core/database/src/main/java/io/github/shinhyo/brba/core/database/di/DaoModule.kt index a3223c8..ac61acd 100644 --- a/core/database/src/main/java/io/github/shinhyo/brba/core/database/di/DaoModule.kt +++ b/core/database/src/main/java/io/github/shinhyo/brba/core/database/di/DaoModule.kt @@ -30,6 +30,6 @@ object DaoModule { @Provides @Singleton fun provideCharacterDao( - appDatabase: AppDatabase + appDatabase: AppDatabase, ): CharacterDao = appDatabase.characterDao() } \ No newline at end of file diff --git a/core/database/src/main/java/io/github/shinhyo/brba/core/database/di/DatabaseModule.kt b/core/database/src/main/java/io/github/shinhyo/brba/core/database/di/DatabaseModule.kt index ac91ca1..d416643 100644 --- a/core/database/src/main/java/io/github/shinhyo/brba/core/database/di/DatabaseModule.kt +++ b/core/database/src/main/java/io/github/shinhyo/brba/core/database/di/DatabaseModule.kt @@ -32,10 +32,10 @@ object DatabaseModule { @Provides @Singleton fun provideAppDatabase( - @ApplicationContext appContext: Context + @ApplicationContext appContext: Context, ): AppDatabase = Room.databaseBuilder( appContext, AppDatabase::class.java, - AppDatabase.NAME + AppDatabase.NAME, ).build() } \ No newline at end of file diff --git a/core/database/src/main/java/io/github/shinhyo/brba/core/database/model/CharacterEntity.kt b/core/database/src/main/java/io/github/shinhyo/brba/core/database/model/CharacterEntity.kt index 91d333c..4d52a37 100644 --- a/core/database/src/main/java/io/github/shinhyo/brba/core/database/model/CharacterEntity.kt +++ b/core/database/src/main/java/io/github/shinhyo/brba/core/database/model/CharacterEntity.kt @@ -26,7 +26,7 @@ import java.util.Date @Keep @Entity( tableName = TABLE_NAME, - indices = [(Index(value = ["charId"], unique = true))] + indices = [(Index(value = ["charId"], unique = true))], ) data class CharacterEntity( @PrimaryKey @@ -35,7 +35,7 @@ data class CharacterEntity( val img: String = "", val nickname: String, val favorite: Boolean = false, - var ctime: Date = Date() + var ctime: Date = Date(), ) { companion object { @@ -49,5 +49,5 @@ fun CharacterEntity.asExternalModel() = BrbaCharacter( img = img, nickname = nickname, isFavorite = favorite, - ctime = ctime + ctime = ctime, ) \ No newline at end of file diff --git a/core/datastore/src/main/AndroidManifest.xml b/core/datastore/src/main/AndroidManifest.xml index a5918e6..e100076 100644 --- a/core/datastore/src/main/AndroidManifest.xml +++ b/core/datastore/src/main/AndroidManifest.xml @@ -1,4 +1,4 @@ - + - \ No newline at end of file + diff --git a/core/datastore/src/main/java/io/github/shinhyo/brba/core/datastore/DeviceDataSource.kt b/core/datastore/src/main/java/io/github/shinhyo/brba/core/datastore/DeviceDataSource.kt index 8887428..cbfccf2 100644 --- a/core/datastore/src/main/java/io/github/shinhyo/brba/core/datastore/DeviceDataSource.kt +++ b/core/datastore/src/main/java/io/github/shinhyo/brba/core/datastore/DeviceDataSource.kt @@ -21,11 +21,11 @@ import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.intPreferencesKey import io.github.shinhyo.brba.core.datastore.model.DeviceData import io.github.shinhyo.brba.core.model.BrbaThemeMode -import javax.inject.Inject import kotlinx.coroutines.flow.map +import javax.inject.Inject class DeviceDataSource @Inject constructor( - private val dataStore: DataStore + private val dataStore: DataStore, ) { object PreferencesKey { @@ -36,7 +36,7 @@ class DeviceDataSource @Inject constructor( DeviceData( themeMode = preferences[PreferencesKey.MODE_THEME]?.let { BrbaThemeMode.entries[it] - } ?: BrbaThemeMode.Dark + } ?: BrbaThemeMode.Dark, ) } diff --git a/core/datastore/src/main/java/io/github/shinhyo/brba/core/datastore/di/DataStoreModule.kt b/core/datastore/src/main/java/io/github/shinhyo/brba/core/datastore/di/DataStoreModule.kt index ba2417d..6b1a758 100644 --- a/core/datastore/src/main/java/io/github/shinhyo/brba/core/datastore/di/DataStoreModule.kt +++ b/core/datastore/src/main/java/io/github/shinhyo/brba/core/datastore/di/DataStoreModule.kt @@ -32,11 +32,13 @@ object DataStoreModule { private const val PREFERENCES_DEVICE = "PREFERENCES_DEVICE" - private val Context.deviceDataStore: DataStore by preferencesDataStore(PREFERENCES_DEVICE) + private val Context.deviceDataStore: DataStore by preferencesDataStore( + PREFERENCES_DEVICE, + ) @Provides @Singleton fun provideDeviceDataStore( - @ApplicationContext context: Context + @ApplicationContext context: Context, ): DataStore = context.deviceDataStore } \ No newline at end of file diff --git a/core/datastore/src/main/java/io/github/shinhyo/brba/core/datastore/model/DeviceData.kt b/core/datastore/src/main/java/io/github/shinhyo/brba/core/datastore/model/DeviceData.kt index 158710e..1cb8207 100644 --- a/core/datastore/src/main/java/io/github/shinhyo/brba/core/datastore/model/DeviceData.kt +++ b/core/datastore/src/main/java/io/github/shinhyo/brba/core/datastore/model/DeviceData.kt @@ -18,5 +18,5 @@ package io.github.shinhyo.brba.core.datastore.model import io.github.shinhyo.brba.core.model.BrbaThemeMode data class DeviceData( - val themeMode: BrbaThemeMode + val themeMode: BrbaThemeMode, ) \ No newline at end of file diff --git a/core/designsystem/build.gradle.kts b/core/designsystem/build.gradle.kts index 3cb82df..83b21cc 100644 --- a/core/designsystem/build.gradle.kts +++ b/core/designsystem/build.gradle.kts @@ -17,7 +17,7 @@ dependencies { implementation(libs.coil.kt.compose) implementation(libs.androidx.compose.material3) - implementation(libs.androidx.constraintlayout.compose) + implementation(libs.androidx.compose.animation) implementation(libs.haze) debugApi(libs.androidx.compose.ui.tooling) diff --git a/core/designsystem/src/main/java/io/github/shinhyo/brba/core/theme/Shape.kt b/core/designsystem/src/main/java/io/github/shinhyo/brba/core/theme/Shape.kt index cbebbda..c3b993a 100644 --- a/core/designsystem/src/main/java/io/github/shinhyo/brba/core/theme/Shape.kt +++ b/core/designsystem/src/main/java/io/github/shinhyo/brba/core/theme/Shape.kt @@ -22,5 +22,5 @@ import androidx.compose.ui.unit.dp val Shapes = Shapes( small = RoundedCornerShape(4.dp), medium = RoundedCornerShape(4.dp), - large = RoundedCornerShape(0.dp) + large = RoundedCornerShape(0.dp), ) \ No newline at end of file diff --git a/core/designsystem/src/main/java/io/github/shinhyo/brba/core/theme/Theme.kt b/core/designsystem/src/main/java/io/github/shinhyo/brba/core/theme/Theme.kt index 149f596..4d3e397 100644 --- a/core/designsystem/src/main/java/io/github/shinhyo/brba/core/theme/Theme.kt +++ b/core/designsystem/src/main/java/io/github/shinhyo/brba/core/theme/Theme.kt @@ -19,6 +19,9 @@ import android.graphics.Color import androidx.activity.ComponentActivity import androidx.activity.SystemBarStyle import androidx.activity.enableEdgeToEdge +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.AnimatedVisibilityScope +import androidx.compose.animation.SharedTransitionScope import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.MaterialTheme import androidx.compose.material3.darkColorScheme @@ -26,6 +29,7 @@ import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalInspectionMode private val LightColors = lightColorScheme( primary = md_theme_light_primary, @@ -56,7 +60,7 @@ private val LightColors = lightColorScheme( inversePrimary = md_theme_light_inversePrimary, surfaceTint = md_theme_light_surfaceTint, outlineVariant = md_theme_light_outlineVariant, - scrim = md_theme_light_scrim + scrim = md_theme_light_scrim, ) private val DarkColors = darkColorScheme( @@ -88,43 +92,44 @@ private val DarkColors = darkColorScheme( inversePrimary = md_theme_dark_inversePrimary, surfaceTint = md_theme_dark_surfaceTint, outlineVariant = md_theme_dark_outlineVariant, - scrim = md_theme_dark_scrim + scrim = md_theme_dark_scrim, ) @Composable fun BrBaTheme( darkTheme: Boolean = isSystemInDarkTheme(), - content: @Composable () -> Unit + content: @Composable () -> Unit, ) { - val context = LocalContext.current as ComponentActivity + if (!LocalInspectionMode.current) { + val context = LocalContext.current as ComponentActivity + LaunchedEffect(darkTheme) { + // background color + val lightScrim = Color.argb(255, 232, 241, 231) + val darkScrim = Color.argb(255, 35, 44, 37) - LaunchedEffect(darkTheme) { - // background color - val lightScrim = Color.argb(255, 232, 241, 231) - val darkScrim = Color.argb(255, 35, 44, 37) - - context.enableEdgeToEdge( - statusBarStyle = SystemBarStyle.auto( - Color.TRANSPARENT, - Color.TRANSPARENT - ) { darkTheme }, + context.enableEdgeToEdge( + statusBarStyle = SystemBarStyle.auto( + Color.TRANSPARENT, + Color.TRANSPARENT, + ) { darkTheme }, // navigationBarStyle = SystemBarStyle.auto( // Color.TRANSPARENT, // Color.TRANSPARENT, // ) { darkTheme } - navigationBarStyle = if (!darkTheme) { - SystemBarStyle.light( - lightScrim, - darkScrim - ) - } else { - SystemBarStyle.dark( - darkScrim - ) - } - ) + navigationBarStyle = if (!darkTheme) { + SystemBarStyle.light( + lightScrim, + darkScrim, + ) + } else { + SystemBarStyle.dark( + darkScrim, + ) + }, + ) + } } val colors = if (!darkTheme) { @@ -137,6 +142,21 @@ fun BrBaTheme( typography = Typography, shapes = Shapes, colorScheme = colors, - content = content + content = content, ) +} + +@Composable +fun BrbaPreviewTheme( + content: @Composable SharedTransitionScope.(AnimatedVisibilityScope) -> Unit, +) { + BrBaTheme { + SharedTransitionScope { + AnimatedVisibility( + visible = true, + ) { + content(this) + } + } + } } \ No newline at end of file diff --git a/core/designsystem/src/main/java/io/github/shinhyo/brba/core/theme/Type.kt b/core/designsystem/src/main/java/io/github/shinhyo/brba/core/theme/Type.kt index cb0559a..bb2e0e6 100644 --- a/core/designsystem/src/main/java/io/github/shinhyo/brba/core/theme/Type.kt +++ b/core/designsystem/src/main/java/io/github/shinhyo/brba/core/theme/Type.kt @@ -17,23 +17,4 @@ package io.github.shinhyo.brba.core.theme import androidx.compose.material3.Typography -// Set of Material typography styles to start with -val Typography = Typography( -// body1 = TextStyle( -// fontFamily = FontFamily.Default, -// fontWeight = FontWeight.Normal, -// fontSize = 16.sp -// ) - /* Other default text styles to override - button = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.W500, - fontSize = 14.sp - ), - caption = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 12.sp - ) - */ -) \ No newline at end of file +val Typography = Typography() \ No newline at end of file diff --git a/core/designsystem/src/main/java/io/github/shinhyo/brba/core/ui/BrBaNavigationBar.kt b/core/designsystem/src/main/java/io/github/shinhyo/brba/core/ui/BrBaNavigationBar.kt index 4bac382..97e4771 100644 --- a/core/designsystem/src/main/java/io/github/shinhyo/brba/core/ui/BrBaNavigationBar.kt +++ b/core/designsystem/src/main/java/io/github/shinhyo/brba/core/ui/BrBaNavigationBar.kt @@ -30,12 +30,12 @@ import androidx.compose.ui.unit.dp @Composable fun BrBaNavigationBar( modifier: Modifier = Modifier, - content: @Composable RowScope.() -> Unit + content: @Composable RowScope.() -> Unit, ) { NavigationBar( modifier = modifier, windowInsets = WindowInsets(0.dp), - content = content + content = content, ) } @@ -44,23 +44,22 @@ fun RowScope.BrbaNavigationBarItem( label: String? = null, onSelected: () -> Boolean, onClick: () -> Unit, - icon: @Composable () -> Unit + icon: @Composable () -> Unit, ) { NavigationBarItem( label = { label?.let { Text( text = it, - style = MaterialTheme.typography.bodySmall + style = MaterialTheme.typography.bodySmall, ) } }, -// alwaysShowLabel = false, selected = onSelected.invoke(), onClick = onClick, icon = icon, colors = NavigationBarItemDefaults.colors(), modifier = Modifier - .fillMaxWidth() + .fillMaxWidth(), ) } \ No newline at end of file diff --git a/core/designsystem/src/main/java/io/github/shinhyo/brba/core/ui/BrbaCharacterCard.kt b/core/designsystem/src/main/java/io/github/shinhyo/brba/core/ui/BrbaCharacterCard.kt index e9e9927..d145032 100644 --- a/core/designsystem/src/main/java/io/github/shinhyo/brba/core/ui/BrbaCharacterCard.kt +++ b/core/designsystem/src/main/java/io/github/shinhyo/brba/core/ui/BrbaCharacterCard.kt @@ -15,11 +15,18 @@ */ package io.github.shinhyo.brba.core.ui +import android.content.res.Configuration +import androidx.compose.animation.AnimatedVisibilityScope +import androidx.compose.animation.SharedTransitionScope import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Card @@ -31,108 +38,117 @@ import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.constraintlayout.compose.ConstraintLayout -import androidx.constraintlayout.compose.Dimension import coil.compose.AsyncImage import coil.request.ImageRequest import io.github.shinhyo.brba.core.model.BrbaCharacter +import io.github.shinhyo.brba.core.theme.BrbaPreviewTheme +import io.github.shinhyo.brba.core.utils.brbaSharedElement @Composable -fun BrbaCharacterCard( +fun SharedTransitionScope.BrbaCharacterCard( + modifier: Modifier = Modifier, character: BrbaCharacter, - onCharacterClick: (Long) -> Unit, + animatedVisibilityScope: AnimatedVisibilityScope, + onCharacterClick: (BrbaCharacter) -> Unit, onFavoriteClick: (BrbaCharacter) -> Unit, - modifier: Modifier = Modifier ) { Card( shape = RoundedCornerShape(8.dp), modifier = modifier .aspectRatio(1f / character.ratio) - .clickable { onCharacterClick.invoke(character.charId) } + .clickable { onCharacterClick.invoke(character) }, ) { - ConstraintLayout() { - val (image, name, dim, favorite) = createRefs() - AsyncImage( - model = ImageRequest.Builder(LocalContext.current).data(character.img) - .crossfade(true) - .build(), - contentDescription = null, - contentScale = ContentScale.Crop, - modifier = Modifier - .fillMaxSize() - .constrainAs(image) { - start.linkTo(parent.start) - top.linkTo(parent.top) - end.linkTo(parent.end) - bottom.linkTo(parent.bottom) - } - ) - - Box( - modifier = Modifier - .constrainAs(dim) { - start.linkTo(parent.start) - top.linkTo(parent.top) - end.linkTo(parent.end) - bottom.linkTo(parent.bottom) - width = Dimension.fillToConstraints - height = Dimension.fillToConstraints - } - .background( - Brush.verticalGradient( - 0.6f to Color.Transparent, - 1f to Color(0xA6000000) - ) - ) - ) + Box( + modifier = Modifier + .fillMaxSize(), + ) { + Box(modifier = Modifier.fillMaxSize()) { + AsyncImage( + model = ImageRequest + .Builder(LocalContext.current) + .data(character.img) + .build(), + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = Modifier + .fillMaxSize() + .brbaSharedElement( + isLocalInspectionMode = LocalInspectionMode.current, + animatedVisibilityScope = animatedVisibilityScope, + rememberSharedContentState(key = "character_${character.charId}_card"), + ), + ) + Box( + modifier = Modifier + .fillMaxSize() + .background( + Brush.verticalGradient( + 0.6f to Color.Transparent, + 1f to Color(0xA6000000), + ), + ), + ) + } - BrbaIconFavorite( - enable = character.isFavorite, + Column( modifier = Modifier - .constrainAs(favorite) { - top.linkTo(parent.top, margin = 4.dp) - end.linkTo(parent.end, margin = 4.dp) - } - ) { onFavoriteClick.invoke(character) } + .fillMaxWidth(), + ) { + Row( + modifier = Modifier + .weight(1f) + .fillMaxWidth() + .padding(horizontal = 4.dp, vertical = 4.dp), + horizontalArrangement = Arrangement.End, + ) { + BrbaIconFavorite( + enable = character.isFavorite, + modifier = Modifier, + ) { onFavoriteClick.invoke(character) } + } - Text( - text = character.name, - color = Color.White, - style = MaterialTheme.typography.titleSmall, - textAlign = TextAlign.Center, - modifier = Modifier - .padding(horizontal = 4.dp) - .constrainAs(name) { - centerHorizontallyTo(parent) - bottom.linkTo(parent.bottom, margin = 8.dp) - } - ) + Text( + text = character.name, + color = Color.White, + style = MaterialTheme.typography.titleSmall, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 8.dp) + .padding(horizontal = 4.dp), + ) + } } } } -@Preview(showBackground = true) +@Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable private fun Preview() { - BrbaCharacterCard( - character = BrbaCharacter( - charId = 0, - name = "Walter White", - birthday = "09-07-1958", - img = "https://images.amcnetworks.com/amc.com/wp-content/uploads/2015/04/cast_bb_700x1000_walter-white-lg.jpg", - status = "Presumed dead", - nickname = "Heisenberg", - portrayed = "", - category = "Breaking Bad", - ratio = 1.2f, - isFavorite = true, - ctime = null - ), - onCharacterClick = {}, - onFavoriteClick = {}, - modifier = Modifier - ) + BrbaPreviewTheme { + BrbaCharacterCard( + modifier = Modifier, + character = BrbaCharacter( + charId = 0, + name = "Walter White", + birthday = "09-07-1958", + img = "https://images.amcnetworks.com/amc.com/wp-content/uploads/2015/04/cast_bb_700x1000_walter-white-lg.jpg", + status = "Presumed dead", + nickname = "Heisenberg", + portrayed = "", + category = "Breaking Bad", + ratio = 1.2f, + isFavorite = true, + ctime = null, + ), + animatedVisibilityScope = it, + onCharacterClick = {}, + onFavoriteClick = {}, + ) + } } \ No newline at end of file diff --git a/core/designsystem/src/main/java/io/github/shinhyo/brba/core/ui/BrbaCharacterRow.kt b/core/designsystem/src/main/java/io/github/shinhyo/brba/core/ui/BrbaCharacterRow.kt index 5c0decf..9569fda 100644 --- a/core/designsystem/src/main/java/io/github/shinhyo/brba/core/ui/BrbaCharacterRow.kt +++ b/core/designsystem/src/main/java/io/github/shinhyo/brba/core/ui/BrbaCharacterRow.kt @@ -15,121 +15,132 @@ */ package io.github.shinhyo.brba.core.ui -import androidx.compose.foundation.layout.aspectRatio +import android.content.res.Configuration +import androidx.compose.animation.AnimatedVisibilityScope +import androidx.compose.animation.SharedTransitionScope +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.material3.Card 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.layout.ContentScale import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.constraintlayout.compose.ConstraintLayout -import androidx.constraintlayout.compose.Dimension import coil.compose.AsyncImage import coil.request.ImageRequest import io.github.shinhyo.brba.core.model.BrbaCharacter +import io.github.shinhyo.brba.core.theme.BrbaPreviewTheme +import io.github.shinhyo.brba.core.utils.brbaSharedElement @Composable -fun BrbaCharacterRow( +fun SharedTransitionScope.BrbaCharacterRow( modifier: Modifier = Modifier, character: BrbaCharacter, - onCharacterClick: (Long) -> Unit = {}, - onFavoriteClick: (BrbaCharacter) -> Unit = {} + animatedVisibilityScope: AnimatedVisibilityScope, + onCharacterClick: (BrbaCharacter) -> Unit = {}, + onFavoriteClick: (BrbaCharacter) -> Unit = {}, ) { - Card(onClick = { - onCharacterClick.invoke(character.charId) - }) { - ConstraintLayout( - modifier = modifier - .height(120.dp) - .fillMaxWidth() - - ) { - val (img, name, nickname, favorite) = createRefs() + Card( + onClick = { onCharacterClick.invoke(character) }, + modifier = modifier + .fillMaxWidth() + .height(120.dp), + ) { + Row { AsyncImage( model = ImageRequest.Builder(LocalContext.current) .data(character.img) - .crossfade(true) .build(), contentDescription = null, contentScale = ContentScale.Crop, alignment = Alignment.TopCenter, modifier = Modifier - .aspectRatio(1f) - .constrainAs(img) { - start.linkTo(parent.start) - top.linkTo(parent.top) - bottom.linkTo(parent.bottom) - } - .clip(MaterialTheme.shapes.medium) + .size(120.dp) + .brbaSharedElement( + isLocalInspectionMode = LocalInspectionMode.current, + animatedVisibilityScope = animatedVisibilityScope, + rememberSharedContentState(key = "character_${character.charId}_row"), + ), ) - Text( - text = character.name, - style = MaterialTheme.typography.titleMedium, - overflow = TextOverflow.Ellipsis, - maxLines = 2, - modifier = Modifier - .fillMaxWidth() - .constrainAs(name) { - start.linkTo(img.end, 8.dp) - top.linkTo(img.top, 8.dp) - end.linkTo(parent.end) - width = Dimension.fillToConstraints - } - ) + Spacer(modifier = Modifier.width(4.dp)) - Text( - text = character.nickname, - style = MaterialTheme.typography.bodyLarge, - fontWeight = FontWeight.ExtraLight, - maxLines = 1, - overflow = TextOverflow.Ellipsis, + Column( modifier = Modifier - .fillMaxWidth() - .constrainAs(nickname) { - start.linkTo(name.start) - top.linkTo(name.bottom, 2.dp) - end.linkTo(parent.end) - width = Dimension.fillToConstraints - } - ) + .weight(1f), + ) { + Text( + text = character.name, + style = MaterialTheme.typography.titleMedium, + overflow = TextOverflow.Ellipsis, + maxLines = 2, + modifier = Modifier + .fillMaxWidth(), + ) - BrbaIconFavorite( - enable = character.isFavorite, - modifier = Modifier.constrainAs(favorite) { - end.linkTo(parent.end, 8.dp) - bottom.linkTo(parent.bottom, 8.dp) + Spacer(modifier = Modifier.height(2.dp)) + + Text( + text = character.nickname, + style = MaterialTheme.typography.bodyLarge, + fontWeight = FontWeight.ExtraLight, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier + .fillMaxWidth(), + ) + + Box( + contentAlignment = Alignment.BottomEnd, + modifier = Modifier + .padding(horizontal = 4.dp, vertical = 4.dp) + .fillMaxSize(), + ) { + BrbaIconFavorite( + enable = character.isFavorite, + modifier = Modifier, + ) { onFavoriteClick.invoke(character) } } - ) { onFavoriteClick.invoke(character) } + } } } } -@Preview(showBackground = true) +@Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable private fun Preview() { - BrbaCharacterRow( - character = BrbaCharacter( - charId = 0, - name = "Walter White Walter White Walter White", - birthday = "09-07-1958", - img = "https://~~~.jpg", - status = "Presumed dead", - nickname = "Heisenberg, Heisenberg, Heisenberg, Heisenberg", - portrayed = "", - category = "Breaking Bad", - ratio = 1.5f, - isFavorite = true, - ctime = null + BrbaPreviewTheme { + BrbaCharacterRow( + character = BrbaCharacter( + charId = 0, + name = "Walter White Walter White Walter White", + birthday = "09-07-1958", + img = "https://~~~.jpg", + status = "Presumed dead", + nickname = "Heisenberg, Heisenberg, Heisenberg, Heisenberg", + portrayed = "", + category = "Breaking Bad", + ratio = 1.5f, + isFavorite = true, + ctime = null, + ), + animatedVisibilityScope = it, ) - ) + } } \ No newline at end of file diff --git a/core/designsystem/src/main/java/io/github/shinhyo/brba/core/ui/BrbaEmptyScreen.kt b/core/designsystem/src/main/java/io/github/shinhyo/brba/core/ui/BrbaEmptyScreen.kt index 71dd4ac..804a949 100644 --- a/core/designsystem/src/main/java/io/github/shinhyo/brba/core/ui/BrbaEmptyScreen.kt +++ b/core/designsystem/src/main/java/io/github/shinhyo/brba/core/ui/BrbaEmptyScreen.kt @@ -15,10 +15,13 @@ */ package io.github.shinhyo.brba.core.ui +import android.content.res.Configuration import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.size import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme @@ -28,10 +31,10 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import io.github.shinhyo.brba.core.designsystem.R +import io.github.shinhyo.brba.core.theme.BrbaPreviewTheme @Composable fun BrbaEmptyScreen() { @@ -40,24 +43,27 @@ fun BrbaEmptyScreen() { verticalArrangement = Arrangement.Center, modifier = Modifier .fillMaxWidth() - .fillMaxHeight() + .fillMaxHeight(), ) { Icon( painterResource(id = R.drawable.ic_flask_outline), contentDescription = null, tint = MaterialTheme.colorScheme.secondary, - modifier = Modifier.size(50.dp) + modifier = Modifier.size(50.dp), ) + Spacer(modifier = Modifier.height(4.dp)) Text( - stringResource(R.string.empty), + text = stringResource(R.string.empty), style = MaterialTheme.typography.bodySmall, - fontWeight = FontWeight.Normal ) } } @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable private fun Preview() { - BrbaEmptyScreen() + BrbaPreviewTheme { + BrbaEmptyScreen() + } } \ No newline at end of file diff --git a/core/designsystem/src/main/java/io/github/shinhyo/brba/core/ui/BrbaIconFavorite.kt b/core/designsystem/src/main/java/io/github/shinhyo/brba/core/ui/BrbaIconFavorite.kt index 50c17c7..f5a6897 100644 --- a/core/designsystem/src/main/java/io/github/shinhyo/brba/core/ui/BrbaIconFavorite.kt +++ b/core/designsystem/src/main/java/io/github/shinhyo/brba/core/ui/BrbaIconFavorite.kt @@ -15,8 +15,10 @@ */ package io.github.shinhyo.brba.core.ui +import android.content.res.Configuration import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Favorite @@ -29,13 +31,14 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import io.github.shinhyo.brba.core.theme.BrbaPreviewTheme import io.github.shinhyo.brba.core.theme.favorite @Composable fun BrbaIconFavorite( enable: Boolean, modifier: Modifier = Modifier, - onClick: () -> Unit + onClick: () -> Unit, ) { Icon( imageVector = if (enable) Icons.Filled.Favorite else Icons.Filled.FavoriteBorder, @@ -46,17 +49,28 @@ fun BrbaIconFavorite( .clickable( onClick = onClick, interactionSource = remember { MutableInteractionSource() }, - indication = rememberRipple(bounded = false) - ) + indication = rememberRipple(bounded = false), + ), ) } -@Preview(showBackground = true) +@Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable private fun Preview() { - BrbaIconFavorite( - enable = false, - modifier = Modifier - ) { + BrbaPreviewTheme { + Column { + BrbaIconFavorite( + enable = false, + modifier = Modifier, + ) { + } + + BrbaIconFavorite( + enable = true, + modifier = Modifier, + ) { + } + } } } \ No newline at end of file diff --git a/core/designsystem/src/main/java/io/github/shinhyo/brba/core/ui/BrbaPreference.kt b/core/designsystem/src/main/java/io/github/shinhyo/brba/core/ui/BrbaPreference.kt index 1349095..0537a91 100644 --- a/core/designsystem/src/main/java/io/github/shinhyo/brba/core/ui/BrbaPreference.kt +++ b/core/designsystem/src/main/java/io/github/shinhyo/brba/core/ui/BrbaPreference.kt @@ -34,6 +34,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import io.github.shinhyo.brba.core.theme.BrbaPreviewTheme @Composable fun BrbaPreference( @@ -42,7 +43,7 @@ fun BrbaPreference( title: @Composable () -> Unit, enabled: Boolean = true, summary: @Composable (() -> Unit)? = null, - onClick: (() -> Unit)? = null + onClick: (() -> Unit)? = null, ) { val colorScheme = MaterialTheme.colorScheme val typography = MaterialTheme.typography @@ -53,9 +54,9 @@ fun BrbaPreference( Modifier.clickable(enabled, onClick = onClick) } else { Modifier - } + }, ), - verticalAlignment = Alignment.CenterVertically + verticalAlignment = Alignment.CenterVertically, ) { icon?.let { Box( @@ -66,9 +67,9 @@ fun BrbaPreference( start = 16.dp, top = 16.dp, end = 0.dp, - bottom = 16.dp + bottom = 16.dp, ), - contentAlignment = Alignment.CenterStart + contentAlignment = Alignment.CenterStart, ) { CompositionLocalProvider( LocalContentColor.provides( @@ -76,9 +77,9 @@ fun BrbaPreference( colorScheme.onSurfaceVariant } else { colorScheme.onSurfaceVariant.copy(0.38f) - } + }, ), - content = icon + content = icon, ) } } @@ -90,8 +91,8 @@ fun BrbaPreference( start = if (icon != null) 0.dp else 16.dp, top = 16.dp, end = 16.dp, - bottom = 16.dp - ) + bottom = 16.dp, + ), ) { CompositionLocalProvider( LocalContentColor.provides( @@ -99,34 +100,41 @@ fun BrbaPreference( colorScheme.onSurface } else { colorScheme.onSurface.copy(alpha = 0.38f) - } - ) + }, + ), ) { ProvideTextStyle(value = typography.bodyLarge, content = title) - summary?.let { ProvideTextStyle(value = typography.bodyMedium, content = summary) } + summary?.let { + ProvideTextStyle( + value = typography.bodyMedium, + content = summary, + ) + } } } } } } -@Preview(showBackground = true) +@Preview @Composable private fun Preview() { - BrbaPreference( - enabled = true, - title = { - Text(text = "title") - }, - summary = { - Text(text = "summary") - }, - icon = { - Icon( - imageVector = Icons.AutoMirrored.Default.List, - contentDescription = null - ) - }, - onClick = {} - ) + BrbaPreviewTheme { + BrbaPreference( + enabled = true, + title = { + Text(text = "title") + }, + summary = { + Text(text = "summary") + }, + icon = { + Icon( + imageVector = Icons.AutoMirrored.Default.List, + contentDescription = null, + ) + }, + onClick = {}, + ) + } } \ No newline at end of file diff --git a/core/designsystem/src/main/java/io/github/shinhyo/brba/core/ui/BrbaThemeSelectDialog.kt b/core/designsystem/src/main/java/io/github/shinhyo/brba/core/ui/BrbaThemeSelectDialog.kt index b6636f2..e4cdeac 100644 --- a/core/designsystem/src/main/java/io/github/shinhyo/brba/core/ui/BrbaThemeSelectDialog.kt +++ b/core/designsystem/src/main/java/io/github/shinhyo/brba/core/ui/BrbaThemeSelectDialog.kt @@ -15,6 +15,7 @@ */ package io.github.shinhyo.brba.core.ui +import android.content.res.Configuration import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -41,12 +42,13 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.window.DialogProperties import io.github.shinhyo.brba.core.model.BrbaThemeMode +import io.github.shinhyo.brba.core.theme.BrbaPreviewTheme @Composable fun BrbaThemeSelectDialog( themeMode: BrbaThemeMode, onDismissRequest: (() -> Unit)? = null, - onConfirm: (BrbaThemeMode) -> Unit + onConfirm: (BrbaThemeMode) -> Unit, ) { val typography = MaterialTheme.typography val colorScheme = MaterialTheme.colorScheme @@ -59,7 +61,7 @@ fun BrbaThemeSelectDialog( title = { Text( text = "Theme", - style = typography.headlineSmall + style = typography.headlineSmall, ) }, text = { @@ -68,7 +70,7 @@ fun BrbaThemeSelectDialog( ThemeRow( text = it.name, selected = it.name == mode.name, - onClick = { mode = it } + onClick = { mode = it }, ) } } @@ -81,10 +83,12 @@ fun BrbaThemeSelectDialog( color = colorScheme.primary, modifier = Modifier .padding(horizontal = 8.dp) - .clickable(onClick = { - onConfirm.invoke(mode) - onDismissRequest?.invoke() - }) + .clickable( + onClick = { + onConfirm.invoke(mode) + onDismissRequest?.invoke() + }, + ), ) }, dismissButton = { @@ -94,9 +98,9 @@ fun BrbaThemeSelectDialog( color = colorScheme.primary, modifier = Modifier .padding(horizontal = 8.dp) - .clickable(onClick = { onDismissRequest?.invoke() }) + .clickable(onClick = { onDismissRequest?.invoke() }), ) - } + }, ) } @@ -104,7 +108,7 @@ fun BrbaThemeSelectDialog( private fun ThemeRow( text: String, selected: Boolean, - onClick: () -> Unit + onClick: () -> Unit, ) { Row( Modifier @@ -112,27 +116,30 @@ private fun ThemeRow( .selectable( selected = selected, role = Role.RadioButton, - onClick = onClick + onClick = onClick, ) .padding(12.dp), - verticalAlignment = Alignment.CenterVertically + verticalAlignment = Alignment.CenterVertically, ) { RadioButton( selected = selected, - onClick = null + onClick = null, ) Spacer(Modifier.width(8.dp)) Text(text) } } -@Preview(showBackground = true) +@Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable private fun Preview() { - var themeMode by remember { mutableStateOf(BrbaThemeMode.Dark) } - BrbaThemeSelectDialog( - onDismissRequest = { }, - onConfirm = { themeMode = it }, - themeMode = themeMode - ) + BrbaPreviewTheme { + var themeMode by remember { mutableStateOf(BrbaThemeMode.Dark) } + BrbaThemeSelectDialog( + onDismissRequest = { }, + onConfirm = { themeMode = it }, + themeMode = themeMode, + ) + } } \ No newline at end of file diff --git a/core/designsystem/src/main/java/io/github/shinhyo/brba/core/ui/BrbaTopAppBar.kt b/core/designsystem/src/main/java/io/github/shinhyo/brba/core/ui/BrbaTopAppBar.kt index bd096ef..99d7144 100644 --- a/core/designsystem/src/main/java/io/github/shinhyo/brba/core/ui/BrbaTopAppBar.kt +++ b/core/designsystem/src/main/java/io/github/shinhyo/brba/core/ui/BrbaTopAppBar.kt @@ -39,7 +39,7 @@ fun BrbaTopAppBar( modifier: Modifier = Modifier, title: String? = null, hazeState: HazeState, - actions: @Composable RowScope.() -> Unit = {} + actions: @Composable RowScope.() -> Unit = {}, ) { TopAppBar( title = { @@ -48,7 +48,7 @@ fun BrbaTopAppBar( color = MaterialTheme.colorScheme.primary, style = MaterialTheme.typography.displaySmall, fontWeight = FontWeight.Bold, - modifier = Modifier + modifier = Modifier, ) }, colors = TopAppBarDefaults.mediumTopAppBarColors(Color.Transparent), @@ -58,11 +58,11 @@ fun BrbaTopAppBar( hazeState, style = HazeDefaults.style( blurRadius = 12.dp, - noiseFactor = 0.1f - ) + noiseFactor = 0.1f, + ), ) .fillMaxWidth(), - actions = actions + actions = actions, ) } @@ -71,6 +71,6 @@ fun BrbaTopAppBar( private fun Preview() { BrbaTopAppBar( title = "Title", - hazeState = HazeState() + hazeState = HazeState(), ) } \ No newline at end of file diff --git a/core/designsystem/src/main/java/io/github/shinhyo/brba/core/ui/CircularProgress.kt b/core/designsystem/src/main/java/io/github/shinhyo/brba/core/ui/CircularProgress.kt index 30ffaf5..c8153a8 100644 --- a/core/designsystem/src/main/java/io/github/shinhyo/brba/core/ui/CircularProgress.kt +++ b/core/designsystem/src/main/java/io/github/shinhyo/brba/core/ui/CircularProgress.kt @@ -15,6 +15,7 @@ */ package io.github.shinhyo.brba.core.ui +import android.content.res.Configuration import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size @@ -25,26 +26,30 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import io.github.shinhyo.brba.core.theme.BrbaPreviewTheme @Composable fun BrBaCircleProgress( - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { Box( contentAlignment = Alignment.Center, - modifier = modifier.fillMaxSize() + modifier = modifier.fillMaxSize(), ) { CircularProgressIndicator( strokeWidth = 5.dp, modifier = Modifier .progressSemantics() - .size(48.dp) + .size(48.dp), ) } } @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable private fun Preview() { - BrBaCircleProgress() + BrbaPreviewTheme { + BrBaCircleProgress() + } } \ No newline at end of file diff --git a/core/designsystem/src/main/java/io/github/shinhyo/brba/core/utils/BrbaSharedElement.kt b/core/designsystem/src/main/java/io/github/shinhyo/brba/core/utils/BrbaSharedElement.kt new file mode 100644 index 0000000..113da30 --- /dev/null +++ b/core/designsystem/src/main/java/io/github/shinhyo/brba/core/utils/BrbaSharedElement.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2024 shinhyo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.github.shinhyo.brba.core.utils + +import androidx.compose.animation.AnimatedVisibilityScope +import androidx.compose.animation.SharedTransitionScope +import androidx.compose.ui.Modifier + +context(SharedTransitionScope) +fun Modifier.brbaSharedElement( + isLocalInspectionMode: Boolean, + animatedVisibilityScope: AnimatedVisibilityScope, + vararg state: SharedTransitionScope.SharedContentState, +): Modifier { + if (isLocalInspectionMode) return this + return state.fold(this) { modifier, contentState -> + modifier.sharedElement(contentState, animatedVisibilityScope) + } +} \ No newline at end of file diff --git a/core/designsystem/src/main/res/drawable/ic_theme_dark.xml b/core/designsystem/src/main/res/drawable/ic_theme_dark.xml new file mode 100644 index 0000000..69f6c47 --- /dev/null +++ b/core/designsystem/src/main/res/drawable/ic_theme_dark.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/designsystem/src/main/res/drawable/ic_theme_light.xml b/core/designsystem/src/main/res/drawable/ic_theme_light.xml new file mode 100644 index 0000000..b348b59 --- /dev/null +++ b/core/designsystem/src/main/res/drawable/ic_theme_light.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/designsystem/src/main/res/drawable/ic_theme_light_dark.xml b/core/designsystem/src/main/res/drawable/ic_theme_light_dark.xml new file mode 100644 index 0000000..5d78e11 --- /dev/null +++ b/core/designsystem/src/main/res/drawable/ic_theme_light_dark.xml @@ -0,0 +1,10 @@ + + + + diff --git a/core/domain/src/main/kotlin/io/github/shinhyo/brba/core/domain/usecase/GetCharacterListUseCase.kt b/core/domain/src/main/kotlin/io/github/shinhyo/brba/core/domain/usecase/GetCharacterListUseCase.kt index d860ca9..176d3fa 100644 --- a/core/domain/src/main/kotlin/io/github/shinhyo/brba/core/domain/usecase/GetCharacterListUseCase.kt +++ b/core/domain/src/main/kotlin/io/github/shinhyo/brba/core/domain/usecase/GetCharacterListUseCase.kt @@ -43,11 +43,13 @@ class GetCharacterListUseCase @Inject constructor( it.map { i -> i.copy(ratio = MIN_RATIO + random.nextInt(4) * 0.12f) } } .combine( - repo.getDatabaseList() + repo.getDatabaseList(), ) { listApi, listDb -> listApi.map { item -> - item.copy(isFavorite = listDb.find { it.charId == item.charId }?.isFavorite ?: false) + item.copy( + isFavorite = listDb.find { it.charId == item.charId }?.isFavorite ?: false, + ) } } .flowOn(ioDispatcher) -} +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/io/github/shinhyo/brba/core/domain/usecase/GetCharacterUseCase.kt b/core/domain/src/main/kotlin/io/github/shinhyo/brba/core/domain/usecase/GetCharacterUseCase.kt index 44cb38f..fe9ff16 100644 --- a/core/domain/src/main/kotlin/io/github/shinhyo/brba/core/domain/usecase/GetCharacterUseCase.kt +++ b/core/domain/src/main/kotlin/io/github/shinhyo/brba/core/domain/usecase/GetCharacterUseCase.kt @@ -19,22 +19,22 @@ import io.github.shinhyo.brba.core.common.di.BrbaDispatcher import io.github.shinhyo.brba.core.common.di.Dispatcher import io.github.shinhyo.brba.core.domain.repository.CharactersRepository import io.github.shinhyo.brba.core.model.BrbaCharacter -import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flowOn +import javax.inject.Inject class GetCharacterUseCase @Inject constructor( @Dispatcher(BrbaDispatcher.IO) private val ioDispatcher: CoroutineDispatcher, - private val repo: CharactersRepository + private val repo: CharactersRepository, ) { operator fun invoke( - id: Long + id: Long, ): Flow = repo.getCharacterList(id = id) .combine( - repo.getDatabaseList(id = id) + repo.getDatabaseList(id = id), ) { api: BrbaCharacter, db: BrbaCharacter? -> api.copy(isFavorite = db?.isFavorite ?: false) }.flowOn(ioDispatcher) -} +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/io/github/shinhyo/brba/core/domain/usecase/GetFavoriteListUseCase.kt b/core/domain/src/main/kotlin/io/github/shinhyo/brba/core/domain/usecase/GetFavoriteListUseCase.kt index e96f3d9..42bf06e 100644 --- a/core/domain/src/main/kotlin/io/github/shinhyo/brba/core/domain/usecase/GetFavoriteListUseCase.kt +++ b/core/domain/src/main/kotlin/io/github/shinhyo/brba/core/domain/usecase/GetFavoriteListUseCase.kt @@ -26,11 +26,11 @@ import javax.inject.Inject class GetFavoriteListUseCase @Inject constructor( @Dispatcher(BrbaDispatcher.IO) private val ioDispatcher: CoroutineDispatcher, - private val repo: CharactersRepository + private val repo: CharactersRepository, ) { operator fun invoke( - isAsc: Boolean = true + isAsc: Boolean = true, ): Flow> = repo.getDatabaseList( - isAsc = isAsc + isAsc = isAsc, ).flowOn(ioDispatcher) -} +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/io/github/shinhyo/brba/core/domain/usecase/UpdateFavoriteUseCase.kt b/core/domain/src/main/kotlin/io/github/shinhyo/brba/core/domain/usecase/UpdateFavoriteUseCase.kt index e6d29e0..fd00fdb 100644 --- a/core/domain/src/main/kotlin/io/github/shinhyo/brba/core/domain/usecase/UpdateFavoriteUseCase.kt +++ b/core/domain/src/main/kotlin/io/github/shinhyo/brba/core/domain/usecase/UpdateFavoriteUseCase.kt @@ -26,8 +26,8 @@ import javax.inject.Inject class UpdateFavoriteUseCase @Inject constructor( @Dispatcher(IO) private val ioDispatcher: CoroutineDispatcher, - private val repo: CharactersRepository + private val repo: CharactersRepository, ) { operator fun invoke(character: BrbaCharacter): Flow = repo.updateFavorite(character) .flowOn(ioDispatcher) -} +} \ No newline at end of file diff --git a/core/model/src/main/java/io/github/shinhyo/brba/core/model/BrbaCharacter.kt b/core/model/src/main/java/io/github/shinhyo/brba/core/model/BrbaCharacter.kt index cab8ff0..e96871d 100644 --- a/core/model/src/main/java/io/github/shinhyo/brba/core/model/BrbaCharacter.kt +++ b/core/model/src/main/java/io/github/shinhyo/brba/core/model/BrbaCharacter.kt @@ -29,4 +29,4 @@ data class BrbaCharacter( val ratio: Float = 1f, val isFavorite: Boolean = false, val ctime: Date? = null, -) +) \ No newline at end of file diff --git a/core/model/src/main/java/io/github/shinhyo/brba/core/model/BrbaDeviceData.kt b/core/model/src/main/java/io/github/shinhyo/brba/core/model/BrbaDeviceData.kt index 379bbdc..533b992 100644 --- a/core/model/src/main/java/io/github/shinhyo/brba/core/model/BrbaDeviceData.kt +++ b/core/model/src/main/java/io/github/shinhyo/brba/core/model/BrbaDeviceData.kt @@ -16,5 +16,5 @@ package io.github.shinhyo.brba.core.model data class BrbaDeviceData( - val themeMode: BrbaThemeMode + val themeMode: BrbaThemeMode, ) \ No newline at end of file diff --git a/core/model/src/main/java/io/github/shinhyo/brba/core/model/BrbaThemeMode.kt b/core/model/src/main/java/io/github/shinhyo/brba/core/model/BrbaThemeMode.kt index 2d852e4..4a93180 100644 --- a/core/model/src/main/java/io/github/shinhyo/brba/core/model/BrbaThemeMode.kt +++ b/core/model/src/main/java/io/github/shinhyo/brba/core/model/BrbaThemeMode.kt @@ -18,5 +18,5 @@ package io.github.shinhyo.brba.core.model enum class BrbaThemeMode { Light, Dark, - System -} + System, +} \ No newline at end of file diff --git a/core/network/src/main/java/io/github/shinhyo/brba/core/network/di/NetworkModule.kt b/core/network/src/main/java/io/github/shinhyo/brba/core/network/di/NetworkModule.kt index 01e4b35..f11f9eb 100644 --- a/core/network/src/main/java/io/github/shinhyo/brba/core/network/di/NetworkModule.kt +++ b/core/network/src/main/java/io/github/shinhyo/brba/core/network/di/NetworkModule.kt @@ -28,6 +28,6 @@ interface NetworkModule { @Binds fun bindNetworkDataSource( - network: RetrofitNetwork + network: RetrofitNetwork, ): NetworkDataSource } \ No newline at end of file diff --git a/core/network/src/main/java/io/github/shinhyo/brba/core/network/model/CharacterResponse.kt b/core/network/src/main/java/io/github/shinhyo/brba/core/network/model/CharacterResponse.kt index a3fa28b..dbb4384 100644 --- a/core/network/src/main/java/io/github/shinhyo/brba/core/network/model/CharacterResponse.kt +++ b/core/network/src/main/java/io/github/shinhyo/brba/core/network/model/CharacterResponse.kt @@ -29,7 +29,7 @@ data class CharacterResponse( val status: String?, val nickname: String?, val portrayed: String?, - val category: String? + val category: String?, ) fun CharacterResponse.asExternalModel() = BrbaCharacter( @@ -40,5 +40,5 @@ fun CharacterResponse.asExternalModel() = BrbaCharacter( status = status ?: "", nickname = nickname ?: "", portrayed = portrayed ?: "", - category = category ?: "" + category = category ?: "", ) \ No newline at end of file diff --git a/core/network/src/main/java/io/github/shinhyo/brba/core/network/retrofit/RetrofitNetwork.kt b/core/network/src/main/java/io/github/shinhyo/brba/core/network/retrofit/RetrofitNetwork.kt index 87185e2..12737af 100644 --- a/core/network/src/main/java/io/github/shinhyo/brba/core/network/retrofit/RetrofitNetwork.kt +++ b/core/network/src/main/java/io/github/shinhyo/brba/core/network/retrofit/RetrofitNetwork.kt @@ -20,8 +20,6 @@ import dagger.hilt.android.qualifiers.ApplicationContext import io.github.shinhyo.brba.core.network.BuildConfig import io.github.shinhyo.brba.core.network.NetworkDataSource import io.github.shinhyo.brba.core.network.model.CharacterResponse -import javax.inject.Inject -import javax.inject.Singleton import okhttp3.Cache import okhttp3.Interceptor import okhttp3.OkHttpClient @@ -31,6 +29,8 @@ import retrofit2.converter.gson.GsonConverterFactory import retrofit2.http.GET import retrofit2.http.Path import timber.log.Timber +import javax.inject.Inject +import javax.inject.Singleton private interface BaBrApi { @@ -43,13 +43,13 @@ private interface BaBrApi { @GET(value = "/api/characters/{id}") suspend fun getCharactersById( - @Path("id") id: Long + @Path("id") id: Long, ): List } @Singleton class RetrofitNetwork @Inject constructor( - @ApplicationContext context: Context + @ApplicationContext context: Context, ) : NetworkDataSource { private val baBrApi by lazy { @@ -66,7 +66,7 @@ class RetrofitNetwork @Inject constructor( } .cache(Cache(context.cacheDir, 5L * 1024 * 1024)) .addInterceptor { forceCache(it) } - .build() + .build(), ) .addConverterFactory(GsonConverterFactory.create()) .build() @@ -76,7 +76,7 @@ class RetrofitNetwork @Inject constructor( private fun forceCache(it: Interceptor.Chain, day: Int = 7): Response { val request = it.request().newBuilder().header( "Cache-Control", - "max-stale=" + 60 * 60 * 24 * day + "max-stale=" + 60 * 60 * 24 * day, ).build() val response = it.proceed(request) Timber.d("provideOkHttpClient: response: $response") diff --git a/feature/bottombar/src/main/java/io/github/shinhyo/brba/feature/bottombar/BottomBarScreen.kt b/feature/bottombar/src/main/java/io/github/shinhyo/brba/feature/bottombar/BottomBarScreen.kt index 734edb5..2f0fc48 100644 --- a/feature/bottombar/src/main/java/io/github/shinhyo/brba/feature/bottombar/BottomBarScreen.kt +++ b/feature/bottombar/src/main/java/io/github/shinhyo/brba/feature/bottombar/BottomBarScreen.kt @@ -15,9 +15,8 @@ */ package io.github.shinhyo.brba.feature.bottombar -import androidx.compose.animation.core.tween -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut +import androidx.compose.animation.AnimatedVisibilityScope +import androidx.compose.animation.SharedTransitionScope import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.navigationBarsPadding @@ -36,26 +35,28 @@ import androidx.navigation.compose.rememberNavController import io.github.shinhyo.brba.core.ui.BrBaNavigationBar import io.github.shinhyo.brba.core.ui.BrbaNavigationBarItem import io.github.shinhyo.brba.feature.detail.navigaion.navigateToDetail -import io.github.shinhyo.brba.feature.favorate.navigation.favoriteTab +import io.github.shinhyo.brba.feature.favorate.navigation.favoriteComposable import io.github.shinhyo.brba.feature.favorate.navigation.navigateFavorite import io.github.shinhyo.brba.feature.main.navigation.LIST_ROUTE -import io.github.shinhyo.brba.feature.main.navigation.listTab +import io.github.shinhyo.brba.feature.main.navigation.listComposable import io.github.shinhyo.brba.feature.main.navigation.navigateList import io.github.shinhyo.brba.feature.main.navigation.navigateSetting -import io.github.shinhyo.brba.feature.main.navigation.settingTab +import io.github.shinhyo.brba.feature.main.navigation.settingComposable @Composable -fun BottomBarScreen( +fun SharedTransitionScope.BottomBarScreen( navController: NavHostController, - navTabController: NavHostController = rememberNavController() + navTabController: NavHostController = rememberNavController(), + animatedVisibilityScope: AnimatedVisibilityScope, ) { Scaffold( contentWindowInsets = WindowInsets(0.dp), bottomBar = { - val currentRoute = navTabController.currentBackStackEntryAsState().value?.destination?.route + val currentRoute = + navTabController.currentBackStackEntryAsState().value?.destination?.route BrBaNavigationBar( modifier = Modifier - .navigationBarsPadding() + .navigationBarsPadding(), ) { for (tab in remember { Tab.entries }) { BrbaNavigationBarItem( @@ -71,14 +72,14 @@ fun BottomBarScreen( icon = { Icon( painterResource(id = tab.icon), - contentDescription = tab.label + contentDescription = tab.label, ) - } + }, ) } } }, - modifier = Modifier.fillMaxSize() + modifier = Modifier.fillMaxSize(), ) { paddingValues -> NavHost( navController = navTabController, @@ -86,12 +87,16 @@ fun BottomBarScreen( modifier = Modifier .padding(paddingValues) .fillMaxSize(), - enterTransition = { fadeIn(animationSpec = tween(300)) }, - exitTransition = { fadeOut(animationSpec = tween(300)) } ) { - listTab(navigateToDetail = navController::navigateToDetail) - favoriteTab(navigateToDetail = navController::navigateToDetail) - settingTab() + listComposable( + navigateToDetail = { navController.navigateToDetail(character = it) }, + animatedVisibilityScope = animatedVisibilityScope, + ) + favoriteComposable( + onCharacterClick = { navController.navigateToDetail(character = it) }, + animatedVisibilityScope = animatedVisibilityScope, + ) + settingComposable() } } } \ No newline at end of file diff --git a/feature/bottombar/src/main/java/io/github/shinhyo/brba/feature/bottombar/Tab.kt b/feature/bottombar/src/main/java/io/github/shinhyo/brba/feature/bottombar/Tab.kt index dbb7745..56d97a6 100644 --- a/feature/bottombar/src/main/java/io/github/shinhyo/brba/feature/bottombar/Tab.kt +++ b/feature/bottombar/src/main/java/io/github/shinhyo/brba/feature/bottombar/Tab.kt @@ -22,9 +22,9 @@ import io.github.shinhyo.brba.feature.main.navigation.SETTING_ROUTE enum class Tab( val label: String, val icon: Int, - val route: String + val route: String, ) { LIST(label = "Character", icon = R.drawable.ic_account_cowboy_hat, route = LIST_ROUTE), FAVORITE(label = "Favorite", icon = R.drawable.ic_heart, route = FAVORITE_ROUTE), - SETTING(label = "Setting", icon = R.drawable.ic_flask_empty, route = SETTING_ROUTE) + SETTING(label = "Setting", icon = R.drawable.ic_flask_empty, route = SETTING_ROUTE), } \ No newline at end of file diff --git a/feature/bottombar/src/main/java/io/github/shinhyo/brba/feature/bottombar/navigation/BottomBarNavigation.kt b/feature/bottombar/src/main/java/io/github/shinhyo/brba/feature/bottombar/navigation/BottomBarNavigation.kt index 7cef79a..0262e18 100644 --- a/feature/bottombar/src/main/java/io/github/shinhyo/brba/feature/bottombar/navigation/BottomBarNavigation.kt +++ b/feature/bottombar/src/main/java/io/github/shinhyo/brba/feature/bottombar/navigation/BottomBarNavigation.kt @@ -15,6 +15,7 @@ */ package io.github.shinhyo.brba.feature.bottombar.navigation +import androidx.compose.animation.SharedTransitionScope import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.compose.composable @@ -22,12 +23,16 @@ import io.github.shinhyo.brba.feature.bottombar.BottomBarScreen const val ROUTE_MAIN = "route_main" -fun NavGraphBuilder.bottomBarScreen( - navController: NavHostController +context(SharedTransitionScope) +fun NavGraphBuilder.bottomBardComposable( + navController: NavHostController, ) { composable( - route = ROUTE_MAIN + route = ROUTE_MAIN, ) { - BottomBarScreen(navController = navController) + BottomBarScreen( + navController = navController, + animatedVisibilityScope = this, + ) } } \ No newline at end of file diff --git a/feature/bottombar/src/main/res/values/strings.xml b/feature/bottombar/src/main/res/values/strings.xml index e5f8fdc..7abc06d 100644 --- a/feature/bottombar/src/main/res/values/strings.xml +++ b/feature/bottombar/src/main/res/values/strings.xml @@ -1,2 +1 @@ - - \ No newline at end of file + diff --git a/feature/detail/src/main/kotlin/io/github/shinhyo/brba/feature/detail/DetailScreen.kt b/feature/detail/src/main/kotlin/io/github/shinhyo/brba/feature/detail/DetailScreen.kt index cfa02e8..f93d0df 100644 --- a/feature/detail/src/main/kotlin/io/github/shinhyo/brba/feature/detail/DetailScreen.kt +++ b/feature/detail/src/main/kotlin/io/github/shinhyo/brba/feature/detail/DetailScreen.kt @@ -15,6 +15,9 @@ */ package io.github.shinhyo.brba.feature.detail +import android.content.res.Configuration +import androidx.compose.animation.AnimatedVisibilityScope +import androidx.compose.animation.SharedTransitionScope import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row @@ -28,12 +31,12 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.AssistChip import androidx.compose.material3.AssistChipDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold -import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -41,6 +44,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview @@ -50,70 +54,64 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import coil.compose.AsyncImage import coil.request.ImageRequest import io.github.shinhyo.brba.core.model.BrbaCharacter +import io.github.shinhyo.brba.core.theme.BrbaPreviewTheme import io.github.shinhyo.brba.core.ui.BrBaCircleProgress import io.github.shinhyo.brba.core.ui.BrbaIconFavorite +import io.github.shinhyo.brba.core.utils.brbaSharedElement @Composable -fun DetailRoute( +fun SharedTransitionScope.DetailRoute( modifier: Modifier = Modifier, - viewModel: DetailViewModel = hiltViewModel() + animatedVisibilityScope: AnimatedVisibilityScope, + viewModel: DetailViewModel = hiltViewModel(), ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() DetailScreen( modifier = modifier.statusBarsPadding(), uiState = uiState, - onFavoriteClick = viewModel::updateFavorite + animatedVisibilityScope = animatedVisibilityScope, + onFavoriteClick = viewModel::updateFavorite, ) } @Composable -private fun DetailScreen( +private fun SharedTransitionScope.DetailScreen( modifier: Modifier = Modifier, uiState: DetailUiState, + animatedVisibilityScope: AnimatedVisibilityScope, onFavoriteClick: (BrbaCharacter) -> Unit, ) { Scaffold( contentWindowInsets = WindowInsets(0.dp), - ) { - Surface( + ) { padding -> + LazyColumn( modifier = modifier .fillMaxSize() - .padding(it) + .padding(padding), ) { - when (uiState) { - is DetailUiState.Loading -> { - BrBaCircleProgress(modifier) - } + items(uiState.items) { detailListType -> + when (val type: DetailListType = detailListType) { + is DetailListType.Loading -> { + BrBaCircleProgress() + } - is DetailUiState.Success -> { - val character = uiState.character - LazyColumn { - item { - AsyncImage( - model = ImageRequest.Builder(LocalContext.current) - .data(character.img) - .crossfade(true) - .build(), - contentScale = ContentScale.Crop, - contentDescription = character.name, - alignment = Alignment.TopCenter, - modifier = Modifier - .fillMaxWidth() - .aspectRatio(1f / 1.4f) - ) - } - item { - Extra( - character = character, - clickFavorite = onFavoriteClick - ) - } + is DetailListType.Image -> { + CharacterImage( + animatedVisibilityScope = animatedVisibilityScope, + type = type, + ) } - } - is DetailUiState.Error -> { - uiState.exception?.printStackTrace() + is DetailListType.Description -> { + Description( + character = type.character, + onFavoriteClick = onFavoriteClick, + ) + } + + is DetailListType.Error -> { + } } } } @@ -121,19 +119,41 @@ private fun DetailScreen( } @Composable -private fun Extra( - character: BrbaCharacter, - clickFavorite: (BrbaCharacter) -> Unit +private fun SharedTransitionScope.CharacterImage( + animatedVisibilityScope: AnimatedVisibilityScope, + type: DetailListType.Image, ) { + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(type.image) + .build(), + contentScale = ContentScale.Crop, + contentDescription = null, + alignment = Alignment.TopCenter, + modifier = Modifier + .fillMaxWidth() + .aspectRatio(1f / 1.4f) + .brbaSharedElement( + isLocalInspectionMode = LocalInspectionMode.current, + animatedVisibilityScope = animatedVisibilityScope, + rememberSharedContentState(key = "character_${type.id}_row"), + rememberSharedContentState(key = "character_${type.id}_card"), + ), + ) +} +@Composable +private fun Description( + character: BrbaCharacter, + onFavoriteClick: (BrbaCharacter) -> Unit, +) { Column( modifier = Modifier .fillMaxWidth() - .padding(16.dp) + .padding(16.dp), ) { - Row( - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth(), ) { Text( text = character.name, @@ -141,15 +161,19 @@ private fun Extra( fontWeight = FontWeight.Bold, maxLines = 2, overflow = TextOverflow.Ellipsis, - modifier = Modifier.weight(1.0f), + modifier = Modifier + .weight(1.0f), ) Spacer(modifier = Modifier.width(4.dp)) BrbaIconFavorite( - character.isFavorite, + enable = character.isFavorite, modifier = Modifier - .size(24.dp) - ) { clickFavorite.invoke(character) } + .size(24.dp), + ) { + onFavoriteClick.invoke(character) + } } + Spacer(modifier = Modifier.width(4.dp)) Text( @@ -171,9 +195,11 @@ private fun Extra( @Composable private fun Chips( textList: List, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { - FlowRow(modifier = modifier.fillMaxWidth()) { + FlowRow( + modifier = modifier.fillMaxWidth(), + ) { textList.forEachIndexed { index, text -> if (index != 0) Spacer(modifier = modifier.width(4.dp)) AssistChip( @@ -182,8 +208,7 @@ private fun Chips( Text( text = text, style = MaterialTheme.typography.labelSmall, - color = MaterialTheme.colorScheme.tertiary - + color = MaterialTheme.colorScheme.tertiary, ) }, shape = RoundedCornerShape(16.dp), @@ -196,25 +221,37 @@ private fun Chips( } } -@Preview(showBackground = true) +@Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable private fun Preview() { - DetailScreen( - uiState = DetailUiState.Success( - character = BrbaCharacter( - charId = 0, - name = "Walter White Walter White Walter White Walter White Walter White", - birthday = "09-07-1958", - img = "https://images.amcnetworks.com/amc.com/wp-content/uploads/2015/04/cast_bb_700x1000_walter-white-lg.jpg", - status = "Presumed dead", - nickname = "Heisenberg, Heisenberg, Heisenberg, Heisenberg, Heisenberg, Heisenberg", - portrayed = "", - category = "Breaking Bad1, Call Saul2, Breaking Bad3, Better Call Saul4, Breaking Bad5, Better Call Saul6", - ratio = 1.5f, - isFavorite = false, - ctime = null - ) - ), - onFavoriteClick = {}, - ) -} + BrbaPreviewTheme { + DetailScreen( + animatedVisibilityScope = it, + uiState = DetailUiState.Success( + listOf( + DetailListType.Image( + id = 8569, + image = "https://~~~.jpg", + ), + DetailListType.Description( + character = BrbaCharacter( + charId = 0, + name = "Walter White Walter White Walter White Walter White Walter White", + birthday = "09-07-1958", + img = "https://~~~.jpg", + status = "Presumed dead", + nickname = "Heisenberg, Heisenberg, Heisenberg, Heisenberg, Heisenberg, Heisenberg", + portrayed = "", + category = "Breaking Bad1, Call Saul2, Breaking Bad3, Better Call Saul4, Breaking Bad5, Better Call Saul6", + ratio = 1.5f, + isFavorite = false, + ctime = null, + ), + ), + ), + ), + onFavoriteClick = {}, + ) + } +} \ No newline at end of file diff --git a/feature/detail/src/main/kotlin/io/github/shinhyo/brba/feature/detail/DetailViewModel.kt b/feature/detail/src/main/kotlin/io/github/shinhyo/brba/feature/detail/DetailViewModel.kt index bc15052..7203d47 100644 --- a/feature/detail/src/main/kotlin/io/github/shinhyo/brba/feature/detail/DetailViewModel.kt +++ b/feature/detail/src/main/kotlin/io/github/shinhyo/brba/feature/detail/DetailViewModel.kt @@ -32,10 +32,31 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import javax.inject.Inject -sealed interface DetailUiState { - data class Success(val character: BrbaCharacter) : DetailUiState - data class Error(val exception: Throwable? = null) : DetailUiState - data object Loading : DetailUiState +sealed interface DetailListType { + + data class Image( + val id: Long, + val image: String, + ) : DetailListType + + data object Loading : DetailListType + + data class Description( + val character: BrbaCharacter, + ) : DetailListType + + data class Error( + val exception: Throwable, + ) : DetailListType +} + +sealed class DetailUiState( + open val items: List, +) { + data class Init(override val items: List) : DetailUiState(items) + data class Loading(override val items: List) : DetailUiState(items) + data class Success(override val items: List) : DetailUiState(items) + data class Error(override val items: List) : DetailUiState(items) } @HiltViewModel @@ -45,21 +66,57 @@ class DetailViewModel @Inject constructor( val updateFavoriteUseCase: UpdateFavoriteUseCase, ) : ViewModel() { - private val args by lazy { DetailArgs(savedStateHandle = savedStateHandle) } + private val args: DetailArgs by lazy { DetailArgs(savedStateHandle = savedStateHandle) } - val uiState = getCharacterUseCase(args.characterId) + val uiState = getCharacterUseCase(id = args.id) .asResult() .map { when (it) { - is Result.Loading -> DetailUiState.Loading - is Result.Success -> DetailUiState.Success(it.data) - is Result.Error -> DetailUiState.Error(it.exception) + is Result.Loading -> DetailUiState.Loading( + listOf( + DetailListType.Image( + id = args.id, + image = args.image, + ), + DetailListType.Loading, + ), + ) + + is Result.Success -> { + val character: BrbaCharacter = it.data + DetailUiState.Success( + listOf( + DetailListType.Image( + id = args.id, + image = args.image, + ), + DetailListType.Description( + character = character, + ), + ), + ) + } + + is Result.Error -> DetailUiState.Error( + listOf( + DetailListType.Error( + exception = it.exception, + ), + ), + ) } } .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5_000), - initialValue = DetailUiState.Loading + initialValue = DetailUiState.Init( + listOf( + DetailListType.Image( + id = args.id, + image = args.image, + ), + ), + ), ) fun updateFavorite(character: BrbaCharacter) { @@ -67,4 +124,4 @@ class DetailViewModel @Inject constructor( .catch { e -> e.printStackTrace() } .launchIn(viewModelScope) } -} +} \ No newline at end of file diff --git a/feature/detail/src/main/kotlin/io/github/shinhyo/brba/feature/detail/navigaion/DetailNavigation.kt b/feature/detail/src/main/kotlin/io/github/shinhyo/brba/feature/detail/navigaion/DetailNavigation.kt index 4ae06a5..fced80e 100644 --- a/feature/detail/src/main/kotlin/io/github/shinhyo/brba/feature/detail/navigaion/DetailNavigation.kt +++ b/feature/detail/src/main/kotlin/io/github/shinhyo/brba/feature/detail/navigaion/DetailNavigation.kt @@ -15,34 +15,49 @@ */ package io.github.shinhyo.brba.feature.detail.navigaion +import android.net.Uri +import androidx.compose.animation.SharedTransitionScope import androidx.lifecycle.SavedStateHandle import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavType import androidx.navigation.compose.composable import androidx.navigation.navArgument +import io.github.shinhyo.brba.core.model.BrbaCharacter import io.github.shinhyo.brba.feature.detail.DetailRoute internal const val ARG_ID = "id" +internal const val ARG_IMAGE = "image" + const val DETAIL_ROUTE = "detail_route" -internal class DetailArgs(val characterId: Long) { +internal data class DetailArgs( + val id: Long, + val image: String, +) { constructor(savedStateHandle: SavedStateHandle) : this( - checkNotNull(savedStateHandle.get(ARG_ID)) + id = savedStateHandle.get(ARG_ID)!!, + image = Uri.decode(savedStateHandle.get(ARG_IMAGE))!!, ) } -fun NavController.navigateToDetail(characterId: Long) { - this.navigate("$DETAIL_ROUTE/$characterId") +fun NavController.navigateToDetail(character: BrbaCharacter) { + this.navigate( + route = "$DETAIL_ROUTE/${character.charId}/${Uri.encode(character.img)}", + ) } -fun NavGraphBuilder.detailScreen() { +context(SharedTransitionScope) +fun NavGraphBuilder.detailComposable() { composable( - route = "$DETAIL_ROUTE/{$ARG_ID}", + route = "$DETAIL_ROUTE/{$ARG_ID}/{$ARG_IMAGE}", arguments = listOf( navArgument(ARG_ID) { type = NavType.LongType }, + navArgument(ARG_IMAGE) { type = NavType.StringType }, ), ) { - DetailRoute() + DetailRoute( + animatedVisibilityScope = this, + ) } -} +} \ No newline at end of file diff --git a/feature/favorite/src/main/AndroidManifest.xml b/feature/favorite/src/main/AndroidManifest.xml index a5918e6..e100076 100644 --- a/feature/favorite/src/main/AndroidManifest.xml +++ b/feature/favorite/src/main/AndroidManifest.xml @@ -1,4 +1,4 @@ - + - \ No newline at end of file + diff --git a/feature/favorite/src/main/java/io/github/shinhyo/brba/feature/favorate/FavoriteScreen.kt b/feature/favorite/src/main/java/io/github/shinhyo/brba/feature/favorate/FavoriteScreen.kt index 79336b1..076b0c7 100644 --- a/feature/favorite/src/main/java/io/github/shinhyo/brba/feature/favorate/FavoriteScreen.kt +++ b/feature/favorite/src/main/java/io/github/shinhyo/brba/feature/favorate/FavoriteScreen.kt @@ -15,8 +15,10 @@ */ package io.github.shinhyo.brba.feature.favorate +import android.content.res.Configuration +import androidx.compose.animation.AnimatedVisibilityScope +import androidx.compose.animation.SharedTransitionScope import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.lazy.LazyColumn @@ -36,33 +38,37 @@ import dev.chrisbanes.haze.HazeDefaults import dev.chrisbanes.haze.HazeState import dev.chrisbanes.haze.haze import io.github.shinhyo.brba.core.model.BrbaCharacter +import io.github.shinhyo.brba.core.theme.BrbaPreviewTheme import io.github.shinhyo.brba.core.ui.BrBaCircleProgress import io.github.shinhyo.brba.core.ui.BrbaCharacterRow import io.github.shinhyo.brba.core.ui.BrbaEmptyScreen import io.github.shinhyo.brba.core.ui.BrbaTopAppBar @Composable -fun FavoriteRoute( +fun SharedTransitionScope.FavoriteRoute( modifier: Modifier = Modifier, + animatedVisibilityScope: AnimatedVisibilityScope, viewModel: FavoriteViewModel = hiltViewModel(), - navigateToDetail: (Long) -> Unit + onCharacterClick: (BrbaCharacter) -> Unit, ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() FavoriteScreen( modifier = modifier, uiState = uiState, - onCharacterClick = navigateToDetail, - onFavoriteClick = viewModel::updateFavorite + animatedVisibilityScope = animatedVisibilityScope, + onCharacterClick = onCharacterClick, + onFavoriteClick = viewModel::updateFavorite, ) } @Composable -private fun FavoriteScreen( +private fun SharedTransitionScope.FavoriteScreen( modifier: Modifier = Modifier, uiState: FavoriteUiState, - onCharacterClick: (Long) -> Unit, - onFavoriteClick: (BrbaCharacter) -> Unit = {} + animatedVisibilityScope: AnimatedVisibilityScope, + onCharacterClick: (BrbaCharacter) -> Unit = {}, + onFavoriteClick: (BrbaCharacter) -> Unit = {}, ) { val hazeState: HazeState = remember { HazeState() } @@ -70,12 +76,12 @@ private fun FavoriteScreen( topBar = { BrbaTopAppBar( hazeState = hazeState, - title = "Favorite" + title = "Favorite", ) }, contentWindowInsets = WindowInsets(16.dp, 4.dp, 16.dp, 16.dp), modifier = Modifier - .fillMaxSize() + .fillMaxSize(), ) { contentPadding -> when (uiState) { @@ -88,23 +94,22 @@ private fun FavoriteScreen( modifier = modifier .haze( state = hazeState, - style = HazeDefaults.style(backgroundColor = MaterialTheme.colorScheme.surface) + style = HazeDefaults.style(backgroundColor = MaterialTheme.colorScheme.surface), ), state = rememberLazyListState(), contentPadding = contentPadding, - verticalArrangement = Arrangement.spacedBy(8.dp) + verticalArrangement = Arrangement.spacedBy(8.dp), ) { items( items = uiState.list, - key = { item -> item.charId } + key = { item -> item.charId }, ) { item -> - Column { - BrbaCharacterRow( - character = item, - onCharacterClick = onCharacterClick, - onFavoriteClick = onFavoriteClick - ) - } + BrbaCharacterRow( + character = item, + animatedVisibilityScope = animatedVisibilityScope, + onCharacterClick = onCharacterClick, + onFavoriteClick = onFavoriteClick, + ) } } } @@ -120,27 +125,31 @@ private fun FavoriteScreen( } } -@Preview(showBackground = true) +@Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable private fun Preview() { - FavoriteScreen( - uiState = FavoriteUiState.Success( - list = listOf( - BrbaCharacter( - charId = 0, - name = "Walter White Walter White Walter White", - birthday = "09-07-1958", - img = "https://~~~.jpg", - status = "Presumed dead", - nickname = "Heisenberg, Heisenberg, Heisenberg, Heisenberg", - portrayed = "", - category = "Breaking Bad", - ratio = 1.5f, - isFavorite = true, - ctime = null - ) - ) - ), - onCharacterClick = {} - ) + BrbaPreviewTheme { + FavoriteScreen( + uiState = FavoriteUiState.Success( + list = listOf( + BrbaCharacter( + charId = 0, + name = "Walter White Walter White Walter White", + birthday = "09-07-1958", + img = "https://~~~.jpg", + status = "Presumed dead", + nickname = "Heisenberg, Heisenberg, Heisenberg, Heisenberg", + portrayed = "", + category = "Breaking Bad", + ratio = 1.5f, + isFavorite = true, + ctime = null, + ), + ), + ), + onCharacterClick = {}, + animatedVisibilityScope = it, + ) + } } \ No newline at end of file diff --git a/feature/favorite/src/main/java/io/github/shinhyo/brba/feature/favorate/FavoriteViewModel.kt b/feature/favorite/src/main/java/io/github/shinhyo/brba/feature/favorate/FavoriteViewModel.kt index e19506b..8d6944a 100644 --- a/feature/favorite/src/main/java/io/github/shinhyo/brba/feature/favorate/FavoriteViewModel.kt +++ b/feature/favorite/src/main/java/io/github/shinhyo/brba/feature/favorate/FavoriteViewModel.kt @@ -23,16 +23,16 @@ import io.github.shinhyo.brba.core.common.result.asResult import io.github.shinhyo.brba.core.domain.usecase.GetFavoriteListUseCase import io.github.shinhyo.brba.core.domain.usecase.UpdateFavoriteUseCase import io.github.shinhyo.brba.core.model.BrbaCharacter -import javax.inject.Inject import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn +import javax.inject.Inject sealed interface FavoriteUiState { data class Success( - val list: List + val list: List, ) : FavoriteUiState data object Empty : FavoriteUiState @@ -43,7 +43,7 @@ sealed interface FavoriteUiState { @HiltViewModel class FavoriteViewModel @Inject constructor( getFavoriteListUseCase: GetFavoriteListUseCase, - val updateFavoriteUseCase: UpdateFavoriteUseCase + val updateFavoriteUseCase: UpdateFavoriteUseCase, ) : ViewModel() { val uiState = getFavoriteListUseCase() @@ -65,7 +65,7 @@ class FavoriteViewModel @Inject constructor( .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5_000), - initialValue = FavoriteUiState.Loading + initialValue = FavoriteUiState.Loading, ) fun updateFavorite(character: BrbaCharacter) { diff --git a/feature/favorite/src/main/java/io/github/shinhyo/brba/feature/favorate/navigation/FavoriteNavigation.kt b/feature/favorite/src/main/java/io/github/shinhyo/brba/feature/favorate/navigation/FavoriteNavigation.kt index beeda20..63792d0 100644 --- a/feature/favorite/src/main/java/io/github/shinhyo/brba/feature/favorate/navigation/FavoriteNavigation.kt +++ b/feature/favorite/src/main/java/io/github/shinhyo/brba/feature/favorate/navigation/FavoriteNavigation.kt @@ -15,10 +15,13 @@ */ package io.github.shinhyo.brba.feature.favorate.navigation +import androidx.compose.animation.AnimatedVisibilityScope +import androidx.compose.animation.SharedTransitionScope import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable import androidx.navigation.navOptions +import io.github.shinhyo.brba.core.model.BrbaCharacter import io.github.shinhyo.brba.feature.favorate.FavoriteRoute const val FAVORITE_ROUTE = "favorite_route" @@ -32,18 +35,21 @@ fun NavController.navigateFavorite() { } launchSingleTop = true restoreState = true - } + }, ) } -fun NavGraphBuilder.favoriteTab( - navigateToDetail: (Long) -> Unit +context(SharedTransitionScope) +fun NavGraphBuilder.favoriteComposable( + onCharacterClick: (BrbaCharacter) -> Unit, + animatedVisibilityScope: AnimatedVisibilityScope, ) { composable( - route = FAVORITE_ROUTE + route = FAVORITE_ROUTE, ) { FavoriteRoute( - navigateToDetail = navigateToDetail + onCharacterClick = onCharacterClick, + animatedVisibilityScope = animatedVisibilityScope, ) } } \ No newline at end of file diff --git a/feature/list/src/main/java/io/github/shinhyo/brba/feature/main/ListScreen.kt b/feature/list/src/main/java/io/github/shinhyo/brba/feature/main/ListScreen.kt index fc3da3c..53790a9 100644 --- a/feature/list/src/main/java/io/github/shinhyo/brba/feature/main/ListScreen.kt +++ b/feature/list/src/main/java/io/github/shinhyo/brba/feature/main/ListScreen.kt @@ -15,18 +15,24 @@ */ package io.github.shinhyo.brba.feature.main +import android.content.res.Configuration +import androidx.compose.animation.AnimatedVisibilityScope +import androidx.compose.animation.SharedTransitionScope import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells import androidx.compose.foundation.lazy.staggeredgrid.items +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel @@ -35,15 +41,18 @@ import dev.chrisbanes.haze.HazeDefaults import dev.chrisbanes.haze.HazeState import dev.chrisbanes.haze.haze import io.github.shinhyo.brba.core.model.BrbaCharacter +import io.github.shinhyo.brba.core.model.BrbaThemeMode +import io.github.shinhyo.brba.core.theme.BrbaPreviewTheme import io.github.shinhyo.brba.core.ui.BrBaCircleProgress import io.github.shinhyo.brba.core.ui.BrbaCharacterCard import io.github.shinhyo.brba.core.ui.BrbaTopAppBar @Composable -internal fun ListRoute( +fun SharedTransitionScope.ListRoute( modifier: Modifier = Modifier, + animatedVisibilityScope: AnimatedVisibilityScope, viewModel: ListViewModel = hiltViewModel(), - navigateToDetail: (Long) -> Unit + navigateToDetail: (BrbaCharacter) -> Unit, ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() @@ -51,26 +60,57 @@ internal fun ListRoute( modifier = modifier, uiState = uiState, onCharacterClick = navigateToDetail, - onFavoriteClick = viewModel::updateFavorite + onFavoriteClick = viewModel::onFavoriteClick, + animatedVisibilityScope = animatedVisibilityScope, + onChangeThemeClick = viewModel::onChangeThemeClick, ) } @Composable -private fun ListScreen( +private fun SharedTransitionScope.ListScreen( modifier: Modifier = Modifier, uiState: ListUiState, - onCharacterClick: (Long) -> Unit = {}, - onFavoriteClick: (BrbaCharacter) -> Unit = {} + animatedVisibilityScope: AnimatedVisibilityScope, + onCharacterClick: (BrbaCharacter) -> Unit = {}, + onFavoriteClick: (BrbaCharacter) -> Unit = {}, + onChangeThemeClick: (BrbaThemeMode) -> Unit = {}, ) { val hazeState: HazeState = remember { HazeState() } Scaffold( topBar = { BrbaTopAppBar( - hazeState = hazeState + hazeState = hazeState, + actions = { + when (uiState) { + is ListUiState.Loading, + is ListUiState.Error, + -> { + } + + is ListUiState.Success -> { + if (uiState.themeMode != BrbaThemeMode.System) { + val icTheme = if (uiState.themeMode == BrbaThemeMode.Light) { + io.github.shinhyo.brba.core.designsystem.R.drawable.ic_theme_light + } else { + io.github.shinhyo.brba.core.designsystem.R.drawable.ic_theme_dark + } + IconButton( + onClick = { onChangeThemeClick.invoke(uiState.themeMode) }, + ) { + Icon( + painterResource(id = icTheme), + contentDescription = "ic_theme", + tint = MaterialTheme.colorScheme.tertiary, + ) + } + } + } + } + }, ) }, - contentWindowInsets = WindowInsets(16.dp, 4.dp, 16.dp, 16.dp) + contentWindowInsets = WindowInsets(16.dp, 4.dp, 16.dp, 16.dp), ) { contentPadding -> when (uiState) { @@ -88,23 +128,25 @@ private fun ListScreen( verticalItemSpacing = 6.dp, contentPadding = contentPadding, columns = StaggeredGridCells.Adaptive( - minSize = 100.dp + minSize = 100.dp, ), modifier = modifier .haze( state = hazeState, - style = HazeDefaults.style(backgroundColor = MaterialTheme.colorScheme.surface) + style = HazeDefaults.style(backgroundColor = MaterialTheme.colorScheme.surface), ) - .fillMaxSize() + .fillMaxSize(), ) { items( - items = uiState.list, - key = { it.charId } + items = uiState.characters, + key = { character -> character.charId }, ) { character -> BrbaCharacterCard( + modifier = Modifier, + animatedVisibilityScope = animatedVisibilityScope, character = character, onCharacterClick = onCharacterClick, - onFavoriteClick = onFavoriteClick + onFavoriteClick = onFavoriteClick, ) } } @@ -113,30 +155,35 @@ private fun ListScreen( } } -@Preview(showBackground = false) +@Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable private fun Preview() { - val character = BrbaCharacter( - charId = 0, - name = "Walter White", - birthday = "09-07-1958", - img = "https://images.amcnetworks.com/amc.com/wp-content/uploads/2015/04/cast_bb_700x1000_walter-white-lg.jpg", - status = "Presumed dead", - nickname = "Heisenberg", - portrayed = "", - category = "Breaking Bad", - ratio = 1.2f, - isFavorite = true, - ctime = null - ) - ListScreen( - uiState = ListUiState.Success( - listOf( - character, - character.copy(charId = 1, ratio = 1.8f), - character.copy(charId = 2, ratio = 1.6f, isFavorite = false), - character.copy(charId = 3, ratio = 1.4f, isFavorite = false) - ) + BrbaPreviewTheme { + val character = BrbaCharacter( + charId = 0, + name = "Walter White", + birthday = "09-07-1958", + img = "https://images.amcnetworks.com/amc.com/wp-content/uploads/2015/04/cast_bb_700x1000_walter-white-lg.jpg", + status = "Presumed dead", + nickname = "Heisenberg", + portrayed = "", + category = "Breaking Bad", + ratio = 1.2f, + isFavorite = true, + ctime = null, ) - ) + ListScreen( + uiState = ListUiState.Success( + characters = listOf( + character, + character.copy(charId = 1, ratio = 1.8f), + character.copy(charId = 2, ratio = 1.6f, isFavorite = false), + character.copy(charId = 3, ratio = 1.4f, isFavorite = false), + ), + themeMode = BrbaThemeMode.System, + ), + animatedVisibilityScope = it, + ) + } } \ No newline at end of file diff --git a/feature/list/src/main/java/io/github/shinhyo/brba/feature/main/ListViewModel.kt b/feature/list/src/main/java/io/github/shinhyo/brba/feature/main/ListViewModel.kt index d028ec3..409e5fd 100644 --- a/feature/list/src/main/java/io/github/shinhyo/brba/feature/main/ListViewModel.kt +++ b/feature/list/src/main/java/io/github/shinhyo/brba/feature/main/ListViewModel.kt @@ -20,18 +20,27 @@ import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import io.github.shinhyo.brba.core.common.result.Result import io.github.shinhyo.brba.core.common.result.asResult +import io.github.shinhyo.brba.core.domain.repository.DeviceRepository import io.github.shinhyo.brba.core.domain.usecase.GetCharacterListUseCase import io.github.shinhyo.brba.core.domain.usecase.UpdateFavoriteUseCase import io.github.shinhyo.brba.core.model.BrbaCharacter -import javax.inject.Inject +import io.github.shinhyo.brba.core.model.BrbaThemeMode import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn +import javax.inject.Inject sealed interface ListUiState { - data class Success(val list: List) : ListUiState + data class Success( + val characters: List, + val themeMode: BrbaThemeMode, + ) : ListUiState + data class Error(val exception: Throwable? = null) : ListUiState data object Loading : ListUiState } @@ -39,26 +48,49 @@ sealed interface ListUiState { @HiltViewModel class ListViewModel @Inject constructor( getCharacterListUseCase: GetCharacterListUseCase, - val updateFavoriteUseCase: UpdateFavoriteUseCase + val updateFavoriteUseCase: UpdateFavoriteUseCase, + private val deviceRepository: DeviceRepository, ) : ViewModel() { - val uiState = getCharacterListUseCase() + val uiState = combine( + getCharacterListUseCase(), + deviceRepository.deviceData, + ) { characters, deviceData -> + characters to deviceData.themeMode + } .asResult() - .map { - when (it) { + .map { result -> + when (result) { is Result.Loading -> ListUiState.Loading - is Result.Success -> ListUiState.Success(it.data) - is Result.Error -> ListUiState.Error(it.exception) + is Result.Success -> { + val (character, themeMode) = result.data + ListUiState.Success(character, themeMode) + } + + is Result.Error -> ListUiState.Error(result.exception) } }.stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5_000), - initialValue = ListUiState.Loading + initialValue = ListUiState.Loading, ) - fun updateFavorite(character: BrbaCharacter) { + fun onFavoriteClick(character: BrbaCharacter) { updateFavoriteUseCase(character) .catch { e -> e.printStackTrace() } .launchIn(viewModelScope) } + + fun onChangeThemeClick(currentMode: BrbaThemeMode) { + flowOf( + when (currentMode) { + BrbaThemeMode.Light -> BrbaThemeMode.Dark + BrbaThemeMode.Dark -> BrbaThemeMode.Light + BrbaThemeMode.System -> throw IllegalArgumentException() + }, + ) + .onEach { mode -> deviceRepository.setThemeMode(mode) } + .catch { e -> e.printStackTrace() } + .launchIn(viewModelScope) + } } \ No newline at end of file diff --git a/feature/list/src/main/java/io/github/shinhyo/brba/feature/main/navigation/ListNavigation.kt b/feature/list/src/main/java/io/github/shinhyo/brba/feature/main/navigation/ListNavigation.kt index 1edad79..0e3fe31 100644 --- a/feature/list/src/main/java/io/github/shinhyo/brba/feature/main/navigation/ListNavigation.kt +++ b/feature/list/src/main/java/io/github/shinhyo/brba/feature/main/navigation/ListNavigation.kt @@ -15,10 +15,13 @@ */ package io.github.shinhyo.brba.feature.main.navigation +import androidx.compose.animation.AnimatedVisibilityScope +import androidx.compose.animation.SharedTransitionScope import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable import androidx.navigation.navOptions +import io.github.shinhyo.brba.core.model.BrbaCharacter import io.github.shinhyo.brba.feature.main.ListRoute const val LIST_ROUTE = "list_route" @@ -32,18 +35,21 @@ fun NavController.navigateList() { } launchSingleTop = true restoreState = true - } + }, ) } -fun NavGraphBuilder.listTab( - navigateToDetail: (Long) -> Unit +context(SharedTransitionScope) +fun NavGraphBuilder.listComposable( + navigateToDetail: (BrbaCharacter) -> Unit, + animatedVisibilityScope: AnimatedVisibilityScope, ) { composable( - route = LIST_ROUTE + route = LIST_ROUTE, ) { ListRoute( - navigateToDetail = navigateToDetail + navigateToDetail = navigateToDetail, + animatedVisibilityScope = animatedVisibilityScope, ) } } \ No newline at end of file diff --git a/feature/main/build.gradle.kts b/feature/main/build.gradle.kts index 6e41d40..5960739 100644 --- a/feature/main/build.gradle.kts +++ b/feature/main/build.gradle.kts @@ -18,7 +18,4 @@ dependencies { implementation(libs.androidx.core.splashscreen) implementation(libs.androidx.activity.compose) - -// implementation(libs.androidx.navigation.compose) -// implementation(libs.androidx.hilt.navigation.compose) } diff --git a/feature/main/src/main/java/io/github/shinhyo/brba/main/BrbaApp.kt b/feature/main/src/main/java/io/github/shinhyo/brba/main/BrbaApp.kt index c58d4c0..d69a8ce 100644 --- a/feature/main/src/main/java/io/github/shinhyo/brba/main/BrbaApp.kt +++ b/feature/main/src/main/java/io/github/shinhyo/brba/main/BrbaApp.kt @@ -15,31 +15,29 @@ */ package io.github.shinhyo.brba.main +import androidx.compose.animation.SharedTransitionLayout import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.navigation.compose.NavHost import io.github.shinhyo.brba.feature.bottombar.navigation.ROUTE_MAIN -import io.github.shinhyo.brba.feature.bottombar.navigation.bottomBarScreen -import io.github.shinhyo.brba.feature.detail.navigaion.detailScreen +import io.github.shinhyo.brba.feature.bottombar.navigation.bottomBardComposable +import io.github.shinhyo.brba.feature.detail.navigaion.detailComposable @Composable fun BrbaApp( - appState: BrbaAppState = rememberAppState() + appState: BrbaAppState = rememberAppState(), ) { - BrbaNavGraph(appState) -} - -@Composable -private fun BrbaNavGraph(appState: BrbaAppState) { - NavHost( - navController = appState.navController, - startDestination = ROUTE_MAIN, - modifier = Modifier.fillMaxSize() - ) { - bottomBarScreen( - navController = appState.navController - ) - detailScreen() + SharedTransitionLayout { + NavHost( + navController = appState.navController, + startDestination = ROUTE_MAIN, + modifier = Modifier.fillMaxSize(), + ) { + bottomBardComposable( + navController = appState.navController, + ) + detailComposable() + } } } \ No newline at end of file diff --git a/feature/main/src/main/java/io/github/shinhyo/brba/main/BrbaAppState.kt b/feature/main/src/main/java/io/github/shinhyo/brba/main/BrbaAppState.kt index b931377..c48ac1f 100644 --- a/feature/main/src/main/java/io/github/shinhyo/brba/main/BrbaAppState.kt +++ b/feature/main/src/main/java/io/github/shinhyo/brba/main/BrbaAppState.kt @@ -28,10 +28,10 @@ import kotlinx.coroutines.CoroutineScope @Composable fun rememberAppState( coroutineScope: CoroutineScope = rememberCoroutineScope(), - navController: NavHostController = rememberNavController() + navController: NavHostController = rememberNavController(), ): BrbaAppState = remember( coroutineScope, - navController + navController, ) { BrbaAppState(coroutineScope, navController) } @@ -39,7 +39,7 @@ fun rememberAppState( @Stable class BrbaAppState( val coroutineScope: CoroutineScope, - val navController: NavHostController + val navController: NavHostController, ) { val currentDestination: NavDestination? diff --git a/feature/main/src/main/java/io/github/shinhyo/brba/main/MainActivity.kt b/feature/main/src/main/java/io/github/shinhyo/brba/main/MainActivity.kt index 179c80e..16789bc 100644 --- a/feature/main/src/main/java/io/github/shinhyo/brba/main/MainActivity.kt +++ b/feature/main/src/main/java/io/github/shinhyo/brba/main/MainActivity.kt @@ -71,7 +71,7 @@ class MainActivity : ComponentActivity() { }, content = { BrbaApp() - } + }, ) } } diff --git a/feature/main/src/main/java/io/github/shinhyo/brba/main/MainViewModel.kt b/feature/main/src/main/java/io/github/shinhyo/brba/main/MainViewModel.kt index 9adb86c..e43f71f 100644 --- a/feature/main/src/main/java/io/github/shinhyo/brba/main/MainViewModel.kt +++ b/feature/main/src/main/java/io/github/shinhyo/brba/main/MainViewModel.kt @@ -20,11 +20,11 @@ import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import io.github.shinhyo.brba.core.domain.repository.DeviceRepository import io.github.shinhyo.brba.core.model.BrbaDeviceData -import javax.inject.Inject import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn +import javax.inject.Inject sealed interface MainUiState { data class Success(val deviceData: BrbaDeviceData) : MainUiState @@ -33,7 +33,7 @@ sealed interface MainUiState { @HiltViewModel class MainViewModel @Inject constructor( - deviceRepository: DeviceRepository + deviceRepository: DeviceRepository, ) : ViewModel() { val uiState: StateFlow = deviceRepository.deviceData @@ -41,6 +41,6 @@ class MainViewModel @Inject constructor( .stateIn( scope = viewModelScope, initialValue = MainUiState.Loading, - started = SharingStarted.WhileSubscribed(5000) + started = SharingStarted.WhileSubscribed(5000), ) } \ No newline at end of file diff --git a/feature/setting/src/main/java/io/github/shinhyo/brba/feature/main/SettingScreen.kt b/feature/setting/src/main/java/io/github/shinhyo/brba/feature/main/SettingScreen.kt index 95b3908..4404e0a 100644 --- a/feature/setting/src/main/java/io/github/shinhyo/brba/feature/main/SettingScreen.kt +++ b/feature/setting/src/main/java/io/github/shinhyo/brba/feature/main/SettingScreen.kt @@ -15,6 +15,7 @@ */ package io.github.shinhyo.brba.feature.main +import android.content.res.Configuration import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn @@ -34,23 +35,24 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import dev.chrisbanes.haze.HazeState +import io.github.shinhyo.brba.core.model.BrbaDeviceData import io.github.shinhyo.brba.core.model.BrbaThemeMode +import io.github.shinhyo.brba.core.theme.BrbaPreviewTheme import io.github.shinhyo.brba.core.ui.BrbaPreference import io.github.shinhyo.brba.core.ui.BrbaThemeSelectDialog import io.github.shinhyo.brba.core.ui.BrbaTopAppBar -import io.github.shinhyo.brba.feature.setting.R @Composable internal fun SettingRoute( modifier: Modifier = Modifier, - viewModel: SettingViewModel = hiltViewModel() + viewModel: SettingViewModel = hiltViewModel(), ) { val uiState: SettingUiState by viewModel.uiState.collectAsStateWithLifecycle() SettingScreen( modifier = modifier, uiState = uiState, - changeTheme = viewModel::changeTheme + onChangeThemeClick = viewModel::onChangeThemeClick, ) } @@ -58,7 +60,7 @@ internal fun SettingRoute( private fun SettingScreen( modifier: Modifier = Modifier, uiState: SettingUiState, - changeTheme: (BrbaThemeMode) -> Unit + onChangeThemeClick: (BrbaThemeMode) -> Unit, ) { val hazeState: HazeState = remember { HazeState() } @@ -66,10 +68,10 @@ private fun SettingScreen( topBar = { BrbaTopAppBar( hazeState = hazeState, - title = "Setting" + title = "Setting", ) }, - modifier = modifier + modifier = modifier, ) { when (uiState) { is SettingUiState.Loading -> {} @@ -84,30 +86,30 @@ private fun SettingScreen( showSettingsDialog = false }, onConfirm = { mode -> - changeTheme(mode) + onChangeThemeClick(mode) showSettingsDialog = false - } + }, ) } LazyColumn( modifier = Modifier .padding(it) - .fillMaxSize() + .fillMaxSize(), ) { item { BrbaPreference( icon = { Icon( - painter = painterResource(id = R.drawable.theme_light_dark), + painter = painterResource(id = io.github.shinhyo.brba.core.designsystem.R.drawable.ic_theme_light_dark), tint = MaterialTheme.colorScheme.tertiary, - contentDescription = null + contentDescription = null, ) }, title = { Text(text = "Theme") }, summary = { Text(text = themeMode.name) }, onClick = { showSettingsDialog = true - } + }, ) } } @@ -116,7 +118,18 @@ private fun SettingScreen( } } -@Preview(showBackground = true, showSystemUi = false) +@Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable private fun Preview() { + BrbaPreviewTheme { + SettingScreen( + uiState = SettingUiState.Success( + SettingData( + deviceData = BrbaDeviceData(themeMode = BrbaThemeMode.Dark), + ), + ), + onChangeThemeClick = {}, + ) + } } \ No newline at end of file diff --git a/feature/setting/src/main/java/io/github/shinhyo/brba/feature/main/SettingViewModel.kt b/feature/setting/src/main/java/io/github/shinhyo/brba/feature/main/SettingViewModel.kt index d9c2d2c..a6e5c1e 100644 --- a/feature/setting/src/main/java/io/github/shinhyo/brba/feature/main/SettingViewModel.kt +++ b/feature/setting/src/main/java/io/github/shinhyo/brba/feature/main/SettingViewModel.kt @@ -21,15 +21,15 @@ import dagger.hilt.android.lifecycle.HiltViewModel import io.github.shinhyo.brba.core.domain.repository.DeviceRepository import io.github.shinhyo.brba.core.model.BrbaDeviceData import io.github.shinhyo.brba.core.model.BrbaThemeMode -import javax.inject.Inject import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +import javax.inject.Inject data class SettingData( - val deviceData: BrbaDeviceData + val deviceData: BrbaDeviceData, ) sealed interface SettingUiState { @@ -39,7 +39,7 @@ sealed interface SettingUiState { @HiltViewModel class SettingViewModel @Inject constructor( - private val deviceRepository: DeviceRepository + private val deviceRepository: DeviceRepository, ) : ViewModel() { val uiState: StateFlow = deviceRepository.deviceData @@ -49,10 +49,10 @@ class SettingViewModel @Inject constructor( .stateIn( scope = viewModelScope, initialValue = SettingUiState.Loading, - started = SharingStarted.WhileSubscribed(5000) + started = SharingStarted.WhileSubscribed(5000), ) - fun changeTheme(themeMode: BrbaThemeMode) { + fun onChangeThemeClick(themeMode: BrbaThemeMode) { viewModelScope.launch { deviceRepository.setThemeMode(themeMode) } diff --git a/feature/setting/src/main/java/io/github/shinhyo/brba/feature/main/navigation/SettingNavigation.kt b/feature/setting/src/main/java/io/github/shinhyo/brba/feature/main/navigation/SettingNavigation.kt index 50ea309..5c3a6c6 100644 --- a/feature/setting/src/main/java/io/github/shinhyo/brba/feature/main/navigation/SettingNavigation.kt +++ b/feature/setting/src/main/java/io/github/shinhyo/brba/feature/main/navigation/SettingNavigation.kt @@ -32,13 +32,13 @@ fun NavController.navigateSetting() { } launchSingleTop = true restoreState = true - } + }, ) } -fun NavGraphBuilder.settingTab() { +fun NavGraphBuilder.settingComposable() { composable( - route = SETTING_ROUTE + route = SETTING_ROUTE, ) { SettingRoute() } diff --git a/feature/setting/src/main/res/drawable/theme_light_dark.xml b/feature/setting/src/main/res/drawable/theme_light_dark.xml deleted file mode 100644 index 72638bf..0000000 --- a/feature/setting/src/main/res/drawable/theme_light_dark.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/feature/setting/src/main/res/values/strings.xml b/feature/setting/src/main/res/values/strings.xml index e5f8fdc..7abc06d 100644 --- a/feature/setting/src/main/res/values/strings.xml +++ b/feature/setting/src/main/res/values/strings.xml @@ -1,2 +1 @@ - - \ No newline at end of file + diff --git a/gif/0.gif b/gif/0.gif index 54a09e5..4754efd 100644 Binary files a/gif/0.gif and b/gif/0.gif differ diff --git a/gif/1.gif b/gif/1.gif index 8ecdbc0..a44972f 100644 Binary files a/gif/1.gif and b/gif/1.gif differ diff --git a/gif/2.gif b/gif/2.gif index 6bd8b49..bd3ac27 100644 Binary files a/gif/2.gif and b/gif/2.gif differ diff --git a/gradle/init.gradle.kts b/gradle/init.gradle.kts index fa8ae4b..37bd9d2 100644 --- a/gradle/init.gradle.kts +++ b/gradle/init.gradle.kts @@ -1,7 +1,5 @@ -val ktlintVersion = "0.43.0" - initscript { - val spotlessVersion = "6.11.0" + val spotlessVersion = "6.25.0" repositories { mavenCentral() @@ -12,20 +10,24 @@ initscript { } } -allprojects { - if (this == rootProject) return@allprojects - apply() - extensions.configure { - kotlin { - target("**/*.kt") - targetExclude("**/build/**/*.kt") - ktlint(ktlintVersion).userData( - mapOf( - "android" to "true", - "disabled_rules" to "no-wildcard-imports,import-ordering,max-line-length,final-newline" +rootProject { + allprojects { + if (this == rootProject) return@allprojects + apply() + extensions.configure { + kotlin { + target("**/*.kt") + targetExclude("**/build/**/*.kt") + ktlint().editorConfigOverride( + // https://pinterest.github.io/ktlint/latest/rules/standard + mapOf( + "android" to "true", + "insert_final_newline" to "false", + "ktlint_standard_function-naming" to "disabled", + ), ) - ) - licenseHeaderFile(rootProject.file("spotless/copyright.kt")) + licenseHeaderFile(rootProject.file("spotless/copyright.kt")) + } } } -} \ No newline at end of file +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0a42aac..7526a13 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,31 +3,30 @@ compileSdkVer = "34" targetSdkVer = "34" minSdkVer = "28" -androidGradlePlugin = "8.3.2" -androidTools = "31.3.2" +androidGradlePlugin = "8.4.0" +androidTools = "31.4.0" kotlin = "1.9.23" #https://github.com/google/ksp/releases ksp = "1.9.23-1.0.20" #https://developer.android.com/jetpack/androidx/releases/compose-kotlin -androidxComposeCompiler = "1.5.11" +androidxComposeCompiler = "1.5.13" androidDesugarJdkLibs = "2.0.4" kotlinxCoroutines = "1.8.0" -ktlint = "11.6.1" -androidxCore = "1.12.0" +androidxCore = "1.13.1" androidxCoreSplashscreen = "1.0.1" androidxAppCompat = "1.6.1" -androidxActivity = "1.8.2" +androidxActivity = "1.9.0" androidxStartup = "1.1.1" androidxHiltNavigationCompose = "1.2.0" androidxConstraintlayoutCompose = "1.0.1" androidxLifecycle = "2.7.0" -androidxDataStore = "1.0.0" +androidxDataStore = "1.1.1" -androidxComposeBom = "2024.04.00" -androidxCompose = "1.5.4" +androidxComposeBom = "2024.05.00" +androidxCompose = "1.7.0-alpha08" androidxComposeMaterial3 = "1.1.2" hilt = "2.51.1" @@ -47,10 +46,10 @@ okhttp = "4.12.0" retrofit = "2.11.0" gson = "2.10.1" room = "2.6.1" -turbine = "0.9.0" +turbine = "1.1.0" junit = "1.1.5" -material = "1.11.0" -haze = "0.6.2" +material = "1.12.0" +haze = "0.7.1" [libraries] @@ -64,7 +63,6 @@ android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", ver android-tools-common = { group = "com.android.tools", name = "common", version.ref = "androidTools" } kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } ksp-gradlePlugin = { group = "com.google.devtools.ksp", name = "com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" } -ktlint-gradlePlugin = { group = "org.jlleitschuh.gradle", name = "ktlint-gradle", version.ref = "ktlint" } # android android-desugarJdkLibs = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "androidDesugarJdkLibs" } @@ -74,7 +72,7 @@ androidx-core-splashscreen = { group = "androidx.core", name = "core-splashscree androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidxAppCompat" } androidx-startup = { group = "androidx.startup", name = "startup-runtime", version.ref = "androidxStartup" } androidx-hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "androidxHiltNavigationCompose" } -androidx-constraintlayout-compose = { group = "androidx.constraintlayout", name = "constraintlayout-compose", version.ref = "androidxConstraintlayoutCompose" } +#androidx-constraintlayout-compose = { group = "androidx.constraintlayout", name = "constraintlayout-compose", version.ref = "androidxConstraintlayoutCompose" } androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "androidxActivity" } androidx-lifecycle-runtimeCompose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidxLifecycle" } androidx-lifecycle-viewModelCompose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "androidxLifecycle" } @@ -93,6 +91,7 @@ androidx-compose-ui-testManifest = { group = "androidx.compose.ui", name = "ui-t androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } androidx-compose-ui-util = { group = "androidx.compose.ui", name = "ui-util" } +androidx-compose-animation = { group = "androidx.compose.animation", name = "animation", version.ref = "androidxCompose" } # hilt hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } @@ -138,4 +137,3 @@ android-application = { id = "com.android.application", version.ref = "androidGr android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" } hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } -jlleitschuh-gradle-ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" } \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index ad1b0e7..7999c35 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -31,5 +31,5 @@ include( ":feature:list", ":feature:favorite", ":feature:setting", - ":feature:detail" -) \ No newline at end of file + ":feature:detail", +)