diff --git a/README.md b/README.md index 131a8e3c..0d7e4f19 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ - [Circuit](https://github.com/slackhq/circuit) - ~~Google ML Kit~~ Google Cloud Vision -- Dagger Hilt +- ~~Dagger Hilt~~ Metro - Retrofit, OkHttp3 - Lottie-Compose - Firebase(Analytics, Crashlytics, Remote Config) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 3790c539..bebec272 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,13 +1,16 @@ @file:Suppress("INLINE_FROM_HIGHER_PLATFORM") import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties +import com.google.devtools.ksp.gradle.KspExtension +import org.gradle.kotlin.dsl.configure import java.util.Properties plugins { alias(libs.plugins.booket.android.application) alias(libs.plugins.booket.android.application.compose) - alias(libs.plugins.booket.android.hilt) alias(libs.plugins.booket.android.firebase) + alias(libs.plugins.metro) + alias(libs.plugins.ksp) } android { @@ -63,8 +66,8 @@ composeStabilityAnalyzer { enabled.set(true) } -ksp { - arg("circuit.codegen.mode", "hilt") +extensions.configure { + arg("circuit.codegen.mode", "metro") } dependencies { @@ -75,6 +78,7 @@ dependencies { projects.core.datastore.api, projects.core.datastore.impl, projects.core.designsystem, + projects.core.di, projects.core.model, projects.core.network, projects.core.ui, diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4cc12899..130c54a0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -12,6 +12,7 @@ + tools:targetApi="31" + tools:replace="android:appComponentFactory"> ().create(this) } + override fun newImageLoader(): ImageLoader { return ImageLoader.Builder(this) .diskCache { diff --git a/app/src/main/kotlin/com/ninecraft/booket/ReedFirebaseMessagingService.kt b/app/src/main/kotlin/com/ninecraft/booket/ReedFirebaseMessagingService.kt index 5efc4928..2548c6a2 100644 --- a/app/src/main/kotlin/com/ninecraft/booket/ReedFirebaseMessagingService.kt +++ b/app/src/main/kotlin/com/ninecraft/booket/ReedFirebaseMessagingService.kt @@ -3,6 +3,7 @@ package com.ninecraft.booket import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent +import android.app.Service import android.content.Context import android.content.Intent import androidx.core.app.NotificationCompat @@ -11,20 +12,24 @@ import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage import com.ninecraft.booket.core.data.api.repository.UserRepository import com.ninecraft.booket.core.designsystem.R +import com.ninecraft.booket.core.di.ServiceKey import com.ninecraft.booket.feature.main.MainActivity -import dagger.hilt.android.AndroidEntryPoint +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesIntoMap +import dev.zacsweers.metro.Inject +import dev.zacsweers.metro.binding import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.launch -import javax.inject.Inject -@AndroidEntryPoint -class ReedFirebaseMessagingService : FirebaseMessagingService() { - - @Inject - lateinit var userRepository: UserRepository +@ContributesIntoMap(AppScope::class, binding = binding()) +@ServiceKey(ReedFirebaseMessagingService::class) +@Inject +class ReedFirebaseMessagingService( + private val userRepository: UserRepository, +) : FirebaseMessagingService() { private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) diff --git a/app/src/main/kotlin/com/ninecraft/booket/di/AppGraph.kt b/app/src/main/kotlin/com/ninecraft/booket/di/AppGraph.kt new file mode 100644 index 00000000..ead4d1a1 --- /dev/null +++ b/app/src/main/kotlin/com/ninecraft/booket/di/AppGraph.kt @@ -0,0 +1,31 @@ +package com.ninecraft.booket.di + +import android.app.Activity +import android.app.Service +import android.content.Context +import com.ninecraft.booket.core.di.ApplicationContext +import com.ninecraft.booket.core.di.DataScope +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.DependencyGraph +import dev.zacsweers.metro.Multibinds +import dev.zacsweers.metro.Provider +import dev.zacsweers.metro.Provides +import kotlin.reflect.KClass + +@DependencyGraph( + scope = AppScope::class, + additionalScopes = [DataScope::class], +) +interface AppGraph { + + @Multibinds(allowEmpty = true) + val activityProviders: Map, Provider> + + @Multibinds(allowEmpty = true) + val serviceProviders: Map, Provider> + + @DependencyGraph.Factory + fun interface Factory { + fun create(@ApplicationContext @Provides context: Context): AppGraph + } +} diff --git a/app/src/main/kotlin/com/ninecraft/booket/di/CircuitGraph.kt b/app/src/main/kotlin/com/ninecraft/booket/di/CircuitGraph.kt new file mode 100644 index 00000000..5b48780d --- /dev/null +++ b/app/src/main/kotlin/com/ninecraft/booket/di/CircuitGraph.kt @@ -0,0 +1,52 @@ +package com.ninecraft.booket.di + +import androidx.compose.foundation.background +import androidx.compose.foundation.text.BasicText +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import com.slack.circuit.foundation.Circuit +import com.slack.circuit.foundation.LocalCircuit +import com.slack.circuit.runtime.Navigator +import com.slack.circuit.runtime.presenter.Presenter +import com.slack.circuit.runtime.ui.Ui +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesTo +import dev.zacsweers.metro.Multibinds +import dev.zacsweers.metro.Provides + +@ContributesTo(AppScope::class) +interface CircuitGraph { + + @Multibinds(allowEmpty = true) + fun presenterFactories(): Set + + @Multibinds(allowEmpty = true) + fun uiFactories(): Set + + @Provides + fun provideCircuit( + presenterFactories: Set, + uiFactories: Set, + ): Circuit { + return Circuit.Builder() + .addPresenterFactories(presenterFactories) + .addUiFactories(uiFactories) + .setAnimatedNavDecoratorFactory(CrossFadeNavDecoratorFactory()) + .setOnUnavailableContent { screen, modifier -> + val circuit = LocalCircuit.current + BasicText( + text = """ + Route not available: ${screen.javaClass.name}. + Presenter: ${circuit?.presenter(screen, Navigator.NoOp)?.javaClass} + UI: ${circuit?.ui(screen)?.javaClass} + All presenterFactories: ${circuit?.newBuilder()?.presenterFactories} + All uiFactories: ${circuit?.newBuilder()?.uiFactories} + """ + .trimIndent(), + modifier = modifier.background(Color.Red), + style = TextStyle(color = Color.Yellow), + ) + } + .build() + } +} diff --git a/app/src/main/kotlin/com/ninecraft/booket/di/CircuitModule.kt b/app/src/main/kotlin/com/ninecraft/booket/di/CircuitModule.kt deleted file mode 100644 index 23df4723..00000000 --- a/app/src/main/kotlin/com/ninecraft/booket/di/CircuitModule.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.ninecraft.booket.di - -import com.slack.circuit.foundation.Circuit -import com.slack.circuit.runtime.presenter.Presenter -import com.slack.circuit.runtime.ui.Ui -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.components.ActivityRetainedComponent -import dagger.hilt.android.scopes.ActivityRetainedScoped -import dagger.multibindings.Multibinds - -@Module -@InstallIn(ActivityRetainedComponent::class) -abstract class CircuitModule { - - @Multibinds - abstract fun presenterFactories(): Set - - @Multibinds - abstract fun uiFactories(): Set - - companion object { - @[Provides ActivityRetainedScoped] - fun provideCircuit( - presenterFactories: @JvmSuppressWildcards Set, - uiFactories: @JvmSuppressWildcards Set, - ): Circuit = Circuit.Builder() - .addPresenterFactories(presenterFactories) - .addUiFactories(uiFactories) - .setAnimatedNavDecoratorFactory(CrossFadeNavDecoratorFactory()) - .build() - } -} diff --git a/app/src/main/kotlin/com/ninecraft/booket/di/MetroAppComponentFactory.kt b/app/src/main/kotlin/com/ninecraft/booket/di/MetroAppComponentFactory.kt new file mode 100644 index 00000000..e7bc3fa9 --- /dev/null +++ b/app/src/main/kotlin/com/ninecraft/booket/di/MetroAppComponentFactory.kt @@ -0,0 +1,61 @@ +package com.ninecraft.booket.di + +import android.app.Activity +import android.app.Application +import android.app.Service +import android.content.Intent +import androidx.annotation.Keep +import androidx.core.app.AppComponentFactory +import com.ninecraft.booket.BooketApplication +import dev.zacsweers.metro.Provider +import kotlin.reflect.KClass + +/** + * AppComponentFactory that uses Metro for constructor injection of Activities and Services. + * + * Requires minSdk 28+. For lower versions, use manual graph access. + */ +@Keep +class MetroAppComponentFactory : AppComponentFactory() { + + private inline fun getInstance( + cl: ClassLoader, + className: String, + providers: Map, Provider>, + ): T? { + val clazz = Class.forName(className, false, cl).asSubclass(T::class.java) + val modelProvider = providers[clazz.kotlin] ?: return null + return modelProvider() + } + + override fun instantiateActivityCompat( + cl: ClassLoader, + className: String, + intent: Intent?, + ): Activity { + return getInstance(cl, className, activityProviders) + ?: super.instantiateActivityCompat(cl, className, intent) + } + + override fun instantiateServiceCompat( + cl: ClassLoader, + className: String, + intent: Intent?, + ): Service { + return getInstance(cl, className, serviceProviders) + ?: super.instantiateServiceCompat(cl, className, intent) + } + + override fun instantiateApplicationCompat(cl: ClassLoader, className: String): Application { + val app = super.instantiateApplicationCompat(cl, className) + val appGraph = (app as BooketApplication).appGraph + activityProviders = appGraph.activityProviders + serviceProviders = appGraph.serviceProviders + return app + } + + companion object { + private lateinit var activityProviders: Map, Provider> + private lateinit var serviceProviders: Map, Provider> + } +} diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index 8322598d..e2d61206 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -9,6 +9,7 @@ dependencies { compileOnly(libs.android.gradle.plugin) compileOnly(libs.kotlin.gradle.plugin) compileOnly(libs.compose.compiler.gradle.plugin) + compileOnly(libs.ksp.gradle.plugin) implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location)) } @@ -20,7 +21,6 @@ gradlePlugin { "android.library.compose" to "AndroidLibraryComposeConventionPlugin", "android.feature" to "AndroidFeatureConventionPlugin", "android.firebase" to "AndroidFirebaseConventionPlugin", - "android.hilt" to "AndroidHiltConventionPlugin", "android.retrofit" to "AndroidRetrofitConventionPlugin", "jvm.library" to "JvmLibraryConventionPlugin", "kotlin.library.serialization" to "KotlinLibrarySerializationConventionPlugin", diff --git a/build-logic/src/main/kotlin/AndroidFeatureConventionPlugin.kt b/build-logic/src/main/kotlin/AndroidFeatureConventionPlugin.kt index a45d81ad..057742e8 100644 --- a/build-logic/src/main/kotlin/AndroidFeatureConventionPlugin.kt +++ b/build-logic/src/main/kotlin/AndroidFeatureConventionPlugin.kt @@ -1,4 +1,5 @@ +import com.google.devtools.ksp.gradle.KspExtension import com.ninecraft.booket.convention.api import com.ninecraft.booket.convention.applyPlugins import com.ninecraft.booket.convention.implementation @@ -7,6 +8,7 @@ import com.ninecraft.booket.convention.libs import com.ninecraft.booket.convention.project import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.dependencies internal class AndroidFeatureConventionPlugin : Plugin { @@ -15,9 +17,14 @@ internal class AndroidFeatureConventionPlugin : Plugin { applyPlugins( "booket.android.library", "booket.android.library.compose", - "booket.android.hilt", + "dev.zacsweers.metro", + "com.google.devtools.ksp", ) + extensions.configure { + arg("circuit.codegen.mode", "metro") + } + dependencies { implementation(project(path = ":core:common")) implementation(project(path = ":core:data:api")) diff --git a/build-logic/src/main/kotlin/AndroidHiltConventionPlugin.kt b/build-logic/src/main/kotlin/AndroidHiltConventionPlugin.kt deleted file mode 100644 index 0875e71a..00000000 --- a/build-logic/src/main/kotlin/AndroidHiltConventionPlugin.kt +++ /dev/null @@ -1,24 +0,0 @@ -import com.ninecraft.booket.convention.Plugins -import com.ninecraft.booket.convention.applyPlugins -import com.ninecraft.booket.convention.implementation -import com.ninecraft.booket.convention.ksp -import com.ninecraft.booket.convention.libs -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.kotlin.dsl.dependencies - -internal class AndroidHiltConventionPlugin : Plugin { - override fun apply(target: Project) { - with(target) { - applyPlugins( - Plugins.HILT, - Plugins.KSP, - ) - - dependencies { - implementation(libs.hilt.android) - ksp(libs.hilt.android.compiler) - } - } - } -} diff --git a/build-logic/src/main/kotlin/com/ninecraft/booket/convention/Compose.kt b/build-logic/src/main/kotlin/com/ninecraft/booket/convention/Compose.kt index 2d5831bd..b8e446c5 100644 --- a/build-logic/src/main/kotlin/com/ninecraft/booket/convention/Compose.kt +++ b/build-logic/src/main/kotlin/com/ninecraft/booket/convention/Compose.kt @@ -25,7 +25,7 @@ internal fun Project.configureCompose( reportsDestination.file("build/composeReports") stabilityConfigurationFiles.addAll( - project.layout.projectDirectory.file("stability.config.conf"), + project.rootProject.layout.projectDirectory.file("stability.config.conf"), ) } diff --git a/build-logic/src/main/kotlin/com/ninecraft/booket/convention/Plugins.kt b/build-logic/src/main/kotlin/com/ninecraft/booket/convention/Plugins.kt index e8dc9812..d5407c69 100644 --- a/build-logic/src/main/kotlin/com/ninecraft/booket/convention/Plugins.kt +++ b/build-logic/src/main/kotlin/com/ninecraft/booket/convention/Plugins.kt @@ -12,8 +12,7 @@ object Plugins { const val ANDROID_LIBRARY = "com.android.library" const val COMPOSE_STABILITY_ANALYZER = "com.github.skydoves.compose.stability.analyzer" - - const val HILT = "dagger.hilt.android.plugin" + const val METRO = "dev.zacsweers.metro" const val KSP = "com.google.devtools.ksp" const val GOOGLE_SERVICES = "com.google.gms.google-services" const val FIREBASE_CRASHLYTICS = "com.google.firebase.crashlytics" diff --git a/build.gradle.kts b/build.gradle.kts index 6d346c9a..26975e45 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,7 +12,7 @@ plugins { alias(libs.plugins.kotlin.serialization) apply false alias(libs.plugins.android.application) apply false alias(libs.plugins.android.library) apply false - alias(libs.plugins.hilt) apply false + alias(libs.plugins.metro) apply false alias(libs.plugins.ksp) apply false alias(libs.plugins.google.service) apply false alias(libs.plugins.firebase.crashlytics) apply false diff --git a/core/common/build.gradle.kts b/core/common/build.gradle.kts index ba851fff..95323238 100644 --- a/core/common/build.gradle.kts +++ b/core/common/build.gradle.kts @@ -3,7 +3,7 @@ plugins { alias(libs.plugins.booket.android.library) alias(libs.plugins.booket.android.library.compose) - alias(libs.plugins.booket.android.hilt) + alias(libs.plugins.metro) alias(libs.plugins.booket.android.retrofit) } diff --git a/core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/AnalyticsHelper.kt b/core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/AnalyticsHelper.kt index fde333f1..37a3720d 100644 --- a/core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/AnalyticsHelper.kt +++ b/core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/AnalyticsHelper.kt @@ -3,11 +3,13 @@ package com.ninecraft.booket.core.common.analytics import com.google.firebase.analytics.FirebaseAnalytics import com.google.firebase.analytics.logEvent import com.orhanobut.logger.Logger -import javax.inject.Inject -import javax.inject.Singleton +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.Inject +import dev.zacsweers.metro.SingleIn -@Singleton -class AnalyticsHelper @Inject constructor( +@SingleIn(AppScope::class) +@Inject +class AnalyticsHelper( private val firebaseAnalytics: FirebaseAnalytics, ) { diff --git a/core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/di/AnalyticsGraph.kt b/core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/di/AnalyticsGraph.kt new file mode 100644 index 00000000..ccb55a4b --- /dev/null +++ b/core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/di/AnalyticsGraph.kt @@ -0,0 +1,15 @@ +package com.ninecraft.booket.core.common.analytics.di + +import com.google.firebase.Firebase +import com.google.firebase.analytics.FirebaseAnalytics +import com.google.firebase.analytics.analytics +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesTo +import dev.zacsweers.metro.Provides + +@ContributesTo(AppScope::class) +interface AnalyticsGraph { + + @Provides + fun provideFirebaseAnalytics(): FirebaseAnalytics = Firebase.analytics +} diff --git a/core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/di/AnalyticsModule.kt b/core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/di/AnalyticsModule.kt deleted file mode 100644 index c08fc156..00000000 --- a/core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/di/AnalyticsModule.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.ninecraft.booket.core.common.analytics.di - -import com.google.firebase.Firebase -import com.google.firebase.analytics.FirebaseAnalytics -import com.google.firebase.analytics.analytics -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -object AnalyticsModule { - - @Provides - @Singleton - fun provideFirebaseAnalytics(): FirebaseAnalytics { - return Firebase.analytics - } -} diff --git a/core/data/impl/build.gradle.kts b/core/data/impl/build.gradle.kts index c24e3f2e..20efcadc 100644 --- a/core/data/impl/build.gradle.kts +++ b/core/data/impl/build.gradle.kts @@ -2,7 +2,7 @@ plugins { alias(libs.plugins.booket.android.library) - alias(libs.plugins.booket.android.hilt) + alias(libs.plugins.metro) alias(libs.plugins.booket.kotlin.library.serialization) } @@ -23,6 +23,7 @@ dependencies { projects.core.common, projects.core.data.api, projects.core.datastore.api, + projects.core.di, projects.core.model, projects.core.network, diff --git a/core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/di/RepositoryModule.kt b/core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/di/DataGraph.kt similarity index 50% rename from core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/di/RepositoryModule.kt rename to core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/di/DataGraph.kt index 013d61de..8dc2680c 100644 --- a/core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/di/RepositoryModule.kt +++ b/core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/di/DataGraph.kt @@ -10,33 +10,25 @@ import com.ninecraft.booket.core.data.impl.repository.DefaultBookRepository import com.ninecraft.booket.core.data.impl.repository.DefaultRecordRepository import com.ninecraft.booket.core.data.impl.repository.DefaultRemoteConfigRepository import com.ninecraft.booket.core.data.impl.repository.DefaultUserRepository -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton +import com.ninecraft.booket.core.di.DataScope +import dev.zacsweers.metro.Binds +import dev.zacsweers.metro.ContributesTo -@Module -@InstallIn(SingletonComponent::class) -internal abstract class RepositoryModule { +@ContributesTo(DataScope::class) +interface DataGraph { @Binds - @Singleton - abstract fun bindAuthRepository(defaultAuthRepository: DefaultAuthRepository): AuthRepository + val DefaultAuthRepository.bind: AuthRepository @Binds - @Singleton - abstract fun bindUserRepository(defaultUserRepository: DefaultUserRepository): UserRepository + val DefaultBookRepository.bind: BookRepository @Binds - @Singleton - abstract fun bindBookRepository(defaultBookRepository: DefaultBookRepository): BookRepository + val DefaultRecordRepository.bind: RecordRepository @Binds - @Singleton - abstract fun bindRecordRepository(defaultRecordRepository: DefaultRecordRepository): RecordRepository + val DefaultRemoteConfigRepository.bind: RemoteConfigRepository @Binds - @Singleton - abstract fun bindRemoteConfigRepository(defaultRemoteConfigRepository: DefaultRemoteConfigRepository): RemoteConfigRepository + val DefaultUserRepository.bind: UserRepository } diff --git a/core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/di/FirebaseModule.kt b/core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/di/FirebaseGraph.kt similarity index 69% rename from core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/di/FirebaseModule.kt rename to core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/di/FirebaseGraph.kt index 9c5f5f65..3b8f3f23 100644 --- a/core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/di/FirebaseModule.kt +++ b/core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/di/FirebaseGraph.kt @@ -9,18 +9,15 @@ import com.google.firebase.remoteconfig.FirebaseRemoteConfig import com.google.firebase.remoteconfig.remoteConfig import com.google.firebase.remoteconfig.remoteConfigSettings import com.ninecraft.booket.core.data.impl.BuildConfig -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton +import com.ninecraft.booket.core.di.DataScope +import dev.zacsweers.metro.ContributesTo +import dev.zacsweers.metro.Provides + +@ContributesTo(DataScope::class) +interface FirebaseGraph { -@InstallIn(SingletonComponent::class) -@Module -internal object FirebaseModule { - @Singleton @Provides - fun provideRemoteConfig(): FirebaseRemoteConfig { + fun provideFirebaseRemoteConfig(): FirebaseRemoteConfig { return Firebase.remoteConfig.apply { val configSettings by lazy { remoteConfigSettings { @@ -31,11 +28,9 @@ internal object FirebaseModule { } } - @Singleton @Provides fun provideFirebaseMessaging(): FirebaseMessaging = Firebase.messaging - @Singleton @Provides - fun provideFirebaseInstallation(): FirebaseInstallations = Firebase.installations + fun provideFirebaseInstallations(): FirebaseInstallations = Firebase.installations } diff --git a/core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultAuthRepository.kt b/core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultAuthRepository.kt index 9e560650..4172d9e0 100644 --- a/core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultAuthRepository.kt +++ b/core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultAuthRepository.kt @@ -7,12 +7,16 @@ import com.ninecraft.booket.core.model.AutoLoginState import com.ninecraft.booket.core.model.UserState import com.ninecraft.booket.core.network.request.LoginRequest import com.ninecraft.booket.core.network.service.ReedService +import dev.zacsweers.metro.Inject +import com.ninecraft.booket.core.di.DataScope +import dev.zacsweers.metro.SingleIn import kotlinx.coroutines.flow.map -import javax.inject.Inject private const val KAKAO_PROVIDER_TYPE = "KAKAO" -internal class DefaultAuthRepository @Inject constructor( +@SingleIn(DataScope::class) +@Inject +class DefaultAuthRepository( private val service: ReedService, private val tokenDataSource: TokenDataSource, ) : AuthRepository { diff --git a/core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultBookRepository.kt b/core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultBookRepository.kt index 2f4580c5..33faf5e5 100644 --- a/core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultBookRepository.kt +++ b/core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultBookRepository.kt @@ -7,9 +7,13 @@ import com.ninecraft.booket.core.datastore.api.datasource.BookRecentSearchDataSo import com.ninecraft.booket.core.datastore.api.datasource.LibraryRecentSearchDataSource import com.ninecraft.booket.core.network.request.BookUpsertRequest import com.ninecraft.booket.core.network.service.ReedService -import javax.inject.Inject +import dev.zacsweers.metro.Inject +import com.ninecraft.booket.core.di.DataScope +import dev.zacsweers.metro.SingleIn -internal class DefaultBookRepository @Inject constructor( +@SingleIn(DataScope::class) +@Inject +class DefaultBookRepository( private val service: ReedService, private val bookRecentSearchDataSource: BookRecentSearchDataSource, private val libraryRecentSearchDataSource: LibraryRecentSearchDataSource, diff --git a/core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultRecordRepository.kt b/core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultRecordRepository.kt index 410769d0..966a40db 100644 --- a/core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultRecordRepository.kt +++ b/core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultRecordRepository.kt @@ -6,9 +6,13 @@ import com.ninecraft.booket.core.data.impl.mapper.toModel import com.ninecraft.booket.core.model.ReadingRecordModel import com.ninecraft.booket.core.network.request.RecordRegisterRequest import com.ninecraft.booket.core.network.service.ReedService -import javax.inject.Inject +import dev.zacsweers.metro.Inject +import com.ninecraft.booket.core.di.DataScope +import dev.zacsweers.metro.SingleIn -class DefaultRecordRepository @Inject constructor( +@SingleIn(DataScope::class) +@Inject +class DefaultRecordRepository( private val service: ReedService, ) : RecordRepository { override suspend fun postRecord( diff --git a/core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultRemoteConfigRepository.kt b/core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultRemoteConfigRepository.kt index 7f47a519..ce73e13b 100644 --- a/core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultRemoteConfigRepository.kt +++ b/core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultRemoteConfigRepository.kt @@ -7,10 +7,14 @@ import com.ninecraft.booket.core.common.utils.runSuspendCatching import com.ninecraft.booket.core.data.api.repository.RemoteConfigRepository import com.ninecraft.booket.core.data.impl.BuildConfig import com.orhanobut.logger.Logger +import dev.zacsweers.metro.Inject +import com.ninecraft.booket.core.di.DataScope +import dev.zacsweers.metro.SingleIn import kotlinx.coroutines.tasks.await -import javax.inject.Inject -class DefaultRemoteConfigRepository @Inject constructor( +@SingleIn(DataScope::class) +@Inject +class DefaultRemoteConfigRepository( private val remoteConfig: FirebaseRemoteConfig, ) : RemoteConfigRepository { override suspend fun getLatestVersion(): Result = runSuspendCatching { diff --git a/core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultUserRepository.kt b/core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultUserRepository.kt index 22954832..bd28b976 100644 --- a/core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultUserRepository.kt +++ b/core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultUserRepository.kt @@ -2,6 +2,7 @@ package com.ninecraft.booket.core.data.impl.repository import com.google.firebase.installations.FirebaseInstallations import com.google.firebase.messaging.FirebaseMessaging +import com.ninecraft.booket.core.di.DataScope import com.ninecraft.booket.core.common.utils.runSuspendCatching import com.ninecraft.booket.core.data.api.repository.UserRepository import com.ninecraft.booket.core.data.impl.mapper.toModel @@ -12,12 +13,15 @@ import com.ninecraft.booket.core.network.request.NotificationSettingsRequest import com.ninecraft.booket.core.network.request.TermsAgreementRequest import com.ninecraft.booket.core.network.service.ReedService import com.orhanobut.logger.Logger +import dev.zacsweers.metro.Inject +import dev.zacsweers.metro.SingleIn import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.tasks.await -import javax.inject.Inject -internal class DefaultUserRepository @Inject constructor( +@SingleIn(DataScope::class) +@Inject +class DefaultUserRepository( private val service: ReedService, private val onboardingDataSource: OnboardingDataSource, private val notificationDataSource: NotificationDataSource, diff --git a/core/datastore/impl/build.gradle.kts b/core/datastore/impl/build.gradle.kts index ba16afc8..303441c8 100644 --- a/core/datastore/impl/build.gradle.kts +++ b/core/datastore/impl/build.gradle.kts @@ -2,7 +2,7 @@ plugins { alias(libs.plugins.booket.android.library) - alias(libs.plugins.booket.android.hilt) + alias(libs.plugins.metro) alias(libs.plugins.booket.kotlin.library.serialization) } @@ -16,14 +16,19 @@ android { dependencies { implementations( + projects.core.common, projects.core.datastore.api, + projects.core.di, projects.core.model, - libs.androidx.datastore.preferences, - libs.logger, ) + // API because DataStore is exposed in public API (DataStoreGraph) + // Metro compiler needs to resolve Preferences type across modules + // See: https://github.com/ZacSweers/metro/discussions/1358#discussioncomment-15020091 + api(libs.androidx.datastore.preferences) + androidTestImplementations( libs.androidx.test.ext.junit, libs.androidx.test.runner, diff --git a/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultBookRecentSearchDataSource.kt b/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultBookRecentSearchDataSource.kt index 457ea401..40bab5fd 100644 --- a/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultBookRecentSearchDataSource.kt +++ b/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultBookRecentSearchDataSource.kt @@ -8,13 +8,17 @@ import com.ninecraft.booket.core.datastore.api.datasource.BookRecentSearchDataSo import com.ninecraft.booket.core.datastore.impl.di.BookRecentSearchDataStore import com.ninecraft.booket.core.datastore.impl.util.handleIOException import com.orhanobut.logger.Logger +import dev.zacsweers.metro.Inject +import com.ninecraft.booket.core.di.DataScope +import dev.zacsweers.metro.SingleIn import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import kotlinx.serialization.SerializationException import kotlinx.serialization.json.Json -import javax.inject.Inject -class DefaultBookRecentSearchDataSource @Inject constructor( +@SingleIn(DataScope::class) +@Inject +class DefaultBookRecentSearchDataSource( @BookRecentSearchDataStore private val dataStore: DataStore, ) : BookRecentSearchDataSource { override val recentSearches: Flow> = dataStore.data diff --git a/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultLibraryRecentSearchDataSource.kt b/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultLibraryRecentSearchDataSource.kt index 97cd15d7..ef0e322d 100644 --- a/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultLibraryRecentSearchDataSource.kt +++ b/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultLibraryRecentSearchDataSource.kt @@ -8,13 +8,17 @@ import com.ninecraft.booket.core.datastore.api.datasource.LibraryRecentSearchDat import com.ninecraft.booket.core.datastore.impl.di.LibraryRecentSearchDataStore import com.ninecraft.booket.core.datastore.impl.util.handleIOException import com.orhanobut.logger.Logger +import dev.zacsweers.metro.Inject +import com.ninecraft.booket.core.di.DataScope +import dev.zacsweers.metro.SingleIn import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import kotlinx.serialization.SerializationException import kotlinx.serialization.json.Json -import javax.inject.Inject -class DefaultLibraryRecentSearchDataSource @Inject constructor( +@SingleIn(DataScope::class) +@Inject +class DefaultLibraryRecentSearchDataSource( @LibraryRecentSearchDataStore private val dataStore: DataStore, ) : LibraryRecentSearchDataSource { override val recentSearches: Flow> = dataStore.data diff --git a/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultNotificationDataSource.kt b/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultNotificationDataSource.kt index 22c6d3b4..579426c8 100644 --- a/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultNotificationDataSource.kt +++ b/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultNotificationDataSource.kt @@ -7,11 +7,15 @@ import androidx.datastore.preferences.core.edit import com.ninecraft.booket.core.datastore.api.datasource.NotificationDataSource import com.ninecraft.booket.core.datastore.impl.di.NotificationDataStore import com.ninecraft.booket.core.datastore.impl.util.handleIOException +import dev.zacsweers.metro.Inject +import com.ninecraft.booket.core.di.DataScope +import dev.zacsweers.metro.SingleIn import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map -import javax.inject.Inject -class DefaultNotificationDataSource @Inject constructor( +@SingleIn(DataScope::class) +@Inject +class DefaultNotificationDataSource( @NotificationDataStore private val dataStore: DataStore, ) : NotificationDataSource { override val isUserNotificationEnabled: Flow = dataStore.data diff --git a/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultOnboardingDataSource.kt b/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultOnboardingDataSource.kt index b314d5f8..c4c4dfd2 100644 --- a/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultOnboardingDataSource.kt +++ b/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultOnboardingDataSource.kt @@ -8,11 +8,15 @@ import com.ninecraft.booket.core.datastore.api.datasource.OnboardingDataSource import com.ninecraft.booket.core.model.OnboardingState import com.ninecraft.booket.core.datastore.impl.di.OnboardingDataStore import com.ninecraft.booket.core.datastore.impl.util.handleIOException +import dev.zacsweers.metro.Inject +import com.ninecraft.booket.core.di.DataScope +import dev.zacsweers.metro.SingleIn import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map -import javax.inject.Inject -class DefaultOnboardingDataSource @Inject constructor( +@SingleIn(DataScope::class) +@Inject +class DefaultOnboardingDataSource( @OnboardingDataStore private val dataStore: DataStore, ) : OnboardingDataSource { override val onboardingState: Flow = dataStore.data diff --git a/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultTokenDataSource.kt b/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultTokenDataSource.kt index b5e89b10..c4b179f2 100644 --- a/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultTokenDataSource.kt +++ b/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultTokenDataSource.kt @@ -9,13 +9,17 @@ import com.ninecraft.booket.core.datastore.impl.di.TokenDataStore import com.ninecraft.booket.core.datastore.impl.security.CryptoManager import com.ninecraft.booket.core.datastore.impl.util.handleIOException import com.orhanobut.logger.Logger +import dev.zacsweers.metro.Inject +import com.ninecraft.booket.core.di.DataScope +import dev.zacsweers.metro.SingleIn import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import java.security.GeneralSecurityException -import javax.inject.Inject -class DefaultTokenDataSource @Inject constructor( +@SingleIn(DataScope::class) +@Inject +class DefaultTokenDataSource( @TokenDataStore private val dataStore: DataStore, private val cryptoManager: CryptoManager, ) : TokenDataSource { diff --git a/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/di/DataStoreGraph.kt b/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/di/DataStoreGraph.kt new file mode 100644 index 00000000..a052bb43 --- /dev/null +++ b/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/di/DataStoreGraph.kt @@ -0,0 +1,82 @@ +package com.ninecraft.booket.core.datastore.impl.di + +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.preferencesDataStore +import com.ninecraft.booket.core.datastore.api.datasource.BookRecentSearchDataSource +import com.ninecraft.booket.core.datastore.api.datasource.LibraryRecentSearchDataSource +import com.ninecraft.booket.core.datastore.api.datasource.NotificationDataSource +import com.ninecraft.booket.core.datastore.api.datasource.OnboardingDataSource +import com.ninecraft.booket.core.datastore.api.datasource.TokenDataSource +import com.ninecraft.booket.core.datastore.impl.datasource.DefaultBookRecentSearchDataSource +import com.ninecraft.booket.core.datastore.impl.datasource.DefaultLibraryRecentSearchDataSource +import com.ninecraft.booket.core.datastore.impl.datasource.DefaultNotificationDataSource +import com.ninecraft.booket.core.datastore.impl.datasource.DefaultOnboardingDataSource +import com.ninecraft.booket.core.datastore.impl.datasource.DefaultTokenDataSource +import com.ninecraft.booket.core.di.ApplicationContext +import com.ninecraft.booket.core.di.DataScope +import dev.zacsweers.metro.Binds +import dev.zacsweers.metro.ContributesTo +import dev.zacsweers.metro.Provides + +private const val TOKEN_DATASTORE_NAME = "TOKENS_DATASTORE" +private const val BOOK_RECENT_SEARCH_DATASTORE_NAME = "BOOK_RECENT_SEARCH_DATASTORE" +private const val LIBRARY_RECENT_SEARCH_DATASTORE_NAME = "LIBRARY_RECENT_SEARCH_DATASTORE" +private const val ONBOARDING_DATASTORE_NAME = "ONBOARDING_DATASTORE" +private const val NOTIFICATION_DATASTORE_NAME = "NOTIFICATION_DATASTORE" + +private val Context.tokenDataStore by preferencesDataStore(name = TOKEN_DATASTORE_NAME) +private val Context.bookRecentSearchDataStore by preferencesDataStore(name = BOOK_RECENT_SEARCH_DATASTORE_NAME) +private val Context.libraryRecentSearchDataStore by preferencesDataStore(name = LIBRARY_RECENT_SEARCH_DATASTORE_NAME) +private val Context.onboardingDataStore by preferencesDataStore(name = ONBOARDING_DATASTORE_NAME) +private val Context.notificationDataStore by preferencesDataStore(name = NOTIFICATION_DATASTORE_NAME) + +@ContributesTo(DataScope::class) +interface DataStoreGraph { + + @TokenDataStore + @Provides + fun provideTokenDataStore( + @ApplicationContext context: Context, + ): DataStore = context.tokenDataStore + + @BookRecentSearchDataStore + @Provides + fun provideBookRecentSearchDataStore( + @ApplicationContext context: Context, + ): DataStore = context.bookRecentSearchDataStore + + @LibraryRecentSearchDataStore + @Provides + fun provideLibraryRecentSearchDataStore( + @ApplicationContext context: Context, + ): DataStore = context.libraryRecentSearchDataStore + + @OnboardingDataStore + @Provides + fun provideOnboardingDataStore( + @ApplicationContext context: Context, + ): DataStore = context.onboardingDataStore + + @NotificationDataStore + @Provides + fun provideNotificationDataStore( + @ApplicationContext context: Context, + ): DataStore = context.notificationDataStore + + @Binds + val DefaultTokenDataSource.bind: TokenDataSource + + @Binds + val DefaultBookRecentSearchDataSource.bind: BookRecentSearchDataSource + + @Binds + val DefaultLibraryRecentSearchDataSource.bind: LibraryRecentSearchDataSource + + @Binds + val DefaultOnboardingDataSource.bind: OnboardingDataSource + + @Binds + val DefaultNotificationDataSource.bind: NotificationDataSource +} diff --git a/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/di/DataStoreModule.kt b/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/di/DataStoreModule.kt deleted file mode 100644 index 88a2c3cc..00000000 --- a/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/di/DataStoreModule.kt +++ /dev/null @@ -1,109 +0,0 @@ -package com.ninecraft.booket.core.datastore.impl.di - -import android.content.Context -import androidx.datastore.core.DataStore -import androidx.datastore.preferences.core.Preferences -import androidx.datastore.preferences.preferencesDataStore -import com.ninecraft.booket.core.datastore.api.datasource.BookRecentSearchDataSource -import com.ninecraft.booket.core.datastore.api.datasource.LibraryRecentSearchDataSource -import com.ninecraft.booket.core.datastore.api.datasource.NotificationDataSource -import com.ninecraft.booket.core.datastore.api.datasource.OnboardingDataSource -import com.ninecraft.booket.core.datastore.api.datasource.TokenDataSource -import com.ninecraft.booket.core.datastore.impl.datasource.DefaultLibraryRecentSearchDataSource -import com.ninecraft.booket.core.datastore.impl.datasource.DefaultOnboardingDataSource -import com.ninecraft.booket.core.datastore.impl.datasource.DefaultBookRecentSearchDataSource -import com.ninecraft.booket.core.datastore.impl.datasource.DefaultNotificationDataSource -import com.ninecraft.booket.core.datastore.impl.datasource.DefaultTokenDataSource -import dagger.Binds -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -object DataStoreModule { - private const val TOKEN_DATASTORE_NAME = "TOKENS_DATASTORE" - private const val BOOK_RECENT_SEARCH_DATASTORE_NAME = "BOOK_RECENT_SEARCH_DATASTORE" - private const val LIBRARY_RECENT_SEARCH_DATASTORE_NAME = "LIBRARY_RECENT_SEARCH_DATASTORE" - private const val ONBOARDING_DATASTORE_NAME = "ONBOARDING_DATASTORE" - private const val NOTIFICATION_DATASTORE_NAME = "NOTIFICATION_DATASTORE" - - private val Context.tokenDataStore by preferencesDataStore(name = TOKEN_DATASTORE_NAME) - private val Context.bookRecentSearchDataStore by preferencesDataStore(name = BOOK_RECENT_SEARCH_DATASTORE_NAME) - private val Context.libraryRecentSearchDataStore by preferencesDataStore(name = LIBRARY_RECENT_SEARCH_DATASTORE_NAME) - private val Context.onboardingDataStore by preferencesDataStore(name = ONBOARDING_DATASTORE_NAME) - private val Context.notificationDataStore by preferencesDataStore(name = NOTIFICATION_DATASTORE_NAME) - - @TokenDataStore - @Provides - @Singleton - fun provideTokenDataStore( - @ApplicationContext context: Context, - ): DataStore = context.tokenDataStore - - @BookRecentSearchDataStore - @Provides - @Singleton - fun provideBookRecentSearchDataStore( - @ApplicationContext context: Context, - ): DataStore = context.bookRecentSearchDataStore - - @LibraryRecentSearchDataStore - @Provides - @Singleton - fun provideLibraryRecentSearchDataStore( - @ApplicationContext context: Context, - ): DataStore = context.libraryRecentSearchDataStore - - @OnboardingDataStore - @Provides - @Singleton - fun provideOnboardingDataStore( - @ApplicationContext context: Context, - ): DataStore = context.onboardingDataStore - - @NotificationDataStore - @Provides - @Singleton - fun provideNotificationDataStore( - @ApplicationContext context: Context, - ): DataStore = context.notificationDataStore -} - -@Module -@InstallIn(SingletonComponent::class) -abstract class DataStoreBindModule { - - @Binds - @Singleton - abstract fun bindTokenDataSource( - defaultTokenDataSource: DefaultTokenDataSource, - ): TokenDataSource - - @Binds - @Singleton - abstract fun bindBookRecentSearchDataSource( - defaultBookRecentSearchDataSource: DefaultBookRecentSearchDataSource, - ): BookRecentSearchDataSource - - @Binds - @Singleton - abstract fun bindLibraryRecentSearchDataSource( - defaultLibraryRecentSearchDataSource: DefaultLibraryRecentSearchDataSource, - ): LibraryRecentSearchDataSource - - @Binds - @Singleton - abstract fun bindOnboardingDataSource( - defaultOnboardingDataSource: DefaultOnboardingDataSource, - ): OnboardingDataSource - - @Binds - @Singleton - abstract fun bindNotificationDataSource( - defaultNotificationDataSource: DefaultNotificationDataSource, - ): NotificationDataSource -} diff --git a/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/di/DataStoreQualifier.kt b/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/di/DataStoreQualifier.kt index 8a65ab8c..9dd43f0c 100644 --- a/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/di/DataStoreQualifier.kt +++ b/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/di/DataStoreQualifier.kt @@ -1,6 +1,6 @@ package com.ninecraft.booket.core.datastore.impl.di -import javax.inject.Qualifier +import dev.zacsweers.metro.Qualifier @Qualifier @Retention(AnnotationRetention.BINARY) diff --git a/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/security/CryptoManager.kt b/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/security/CryptoManager.kt index ca5c64b7..cc9acbbe 100644 --- a/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/security/CryptoManager.kt +++ b/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/security/CryptoManager.kt @@ -3,16 +3,18 @@ package com.ninecraft.booket.core.datastore.impl.security import android.security.keystore.KeyGenParameterSpec import android.security.keystore.KeyProperties import android.util.Base64 +import dev.zacsweers.metro.Inject +import com.ninecraft.booket.core.di.DataScope +import dev.zacsweers.metro.SingleIn import java.security.KeyStore import javax.crypto.Cipher import javax.crypto.KeyGenerator import javax.crypto.SecretKey import javax.crypto.spec.IvParameterSpec -import javax.inject.Inject -import javax.inject.Singleton -@Singleton -class CryptoManager @Inject constructor() { +@SingleIn(DataScope::class) +@Inject +class CryptoManager { private val keyStore = KeyStore .getInstance("AndroidKeyStore") .apply { diff --git a/core/designsystem/stability/designsystem.stability b/core/designsystem/stability/designsystem.stability index 319a7836..2bf67bed 100644 --- a/core/designsystem/stability/designsystem.stability +++ b/core/designsystem/stability/designsystem.stability @@ -15,18 +15,12 @@ public fun com.ninecraft.booket.core.designsystem.component.NetworkImage(imageUr - placeholder: RUNTIME (requires runtime check) - contentScale: STABLE (marked @Stable or @Immutable) -@Composable -private fun com.ninecraft.booket.core.designsystem.component.NetworkImagePreview(): kotlin.Unit - skippable: true - restartable: true - params: - @Composable public fun com.ninecraft.booket.core.designsystem.component.RecordProgressBar(currentStep: com.ninecraft.booket.core.designsystem.RecordStep, modifier: androidx.compose.ui.Modifier): kotlin.Unit skippable: true restartable: true params: - - currentStep: STABLE + - currentStep: STABLE (class with no mutable properties) - modifier: STABLE (marked @Stable or @Immutable) @Composable @@ -36,12 +30,6 @@ public fun com.ninecraft.booket.core.designsystem.component.ReedDivider(modifier params: - modifier: STABLE (marked @Stable or @Immutable) -@Composable -private fun com.ninecraft.booket.core.designsystem.component.ReedDividerPreview(): kotlin.Unit - skippable: true - restartable: true - params: - @Composable public fun com.ninecraft.booket.core.designsystem.component.ResourceImage(imageRes: kotlin.Int, contentDescription: kotlin.String, modifier: androidx.compose.ui.Modifier, placeholder: androidx.compose.ui.graphics.painter.Painter?, contentScale: androidx.compose.ui.layout.ContentScale): kotlin.Unit skippable: false @@ -53,12 +41,6 @@ public fun com.ninecraft.booket.core.designsystem.component.ResourceImage(imageR - placeholder: RUNTIME (requires runtime check) - contentScale: STABLE (marked @Stable or @Immutable) -@Composable -private fun com.ninecraft.booket.core.designsystem.component.ResourceImagePreview(): kotlin.Unit - skippable: true - restartable: true - params: - @Composable public fun com.ninecraft.booket.core.designsystem.component.button.(): com.ninecraft.booket.core.designsystem.component.button.ButtonSizeStyle skippable: true @@ -102,8 +84,8 @@ public fun com.ninecraft.booket.core.designsystem.component.button.ReedButton(on params: - onClick: STABLE (function type) - text: STABLE (String is immutable) - - sizeStyle: STABLE - - colorStyle: STABLE + - sizeStyle: STABLE (class with no mutable properties) + - colorStyle: STABLE (class with no mutable properties) - modifier: STABLE (marked @Stable or @Immutable) - enabled: STABLE (primitive type) - leadingIcon: STABLE (composable function type) @@ -141,30 +123,6 @@ public fun com.ninecraft.booket.core.designsystem.component.button.ReedButtonCol restartable: true params: -@Composable -private fun com.ninecraft.booket.core.designsystem.component.button.ReedButtonDisabledPreview(): kotlin.Unit - skippable: true - restartable: true - params: - -@Composable -private fun com.ninecraft.booket.core.designsystem.component.button.ReedLargeButtonPreview(): kotlin.Unit - skippable: true - restartable: true - params: - -@Composable -private fun com.ninecraft.booket.core.designsystem.component.button.ReedMediumButtonPreview(): kotlin.Unit - skippable: true - restartable: true - params: - -@Composable -private fun com.ninecraft.booket.core.designsystem.component.button.ReedSmallButtonPreview(): kotlin.Unit - skippable: true - restartable: true - params: - @Composable public fun com.ninecraft.booket.core.designsystem.component.button.ReedTextButton(onClick: kotlin.Function0, text: kotlin.String, sizeStyle: com.ninecraft.booket.core.designsystem.component.button.ButtonSizeStyle, colorStyle: com.ninecraft.booket.core.designsystem.component.button.ReedButtonColorStyle, modifier: androidx.compose.ui.Modifier, enabled: kotlin.Boolean, multipleEventsCutterEnabled: kotlin.Boolean): kotlin.Unit skippable: true @@ -172,18 +130,12 @@ public fun com.ninecraft.booket.core.designsystem.component.button.ReedTextButto params: - onClick: STABLE (function type) - text: STABLE (String is immutable) - - sizeStyle: STABLE - - colorStyle: STABLE + - sizeStyle: STABLE (class with no mutable properties) + - colorStyle: STABLE (class with no mutable properties) - modifier: STABLE (marked @Stable or @Immutable) - enabled: STABLE (primitive type) - multipleEventsCutterEnabled: STABLE (primitive type) -@Composable -private fun com.ninecraft.booket.core.designsystem.component.button.ReedTextButtonPreview(): kotlin.Unit - skippable: true - restartable: true - params: - @Composable public fun com.ninecraft.booket.core.designsystem.component.checkbox.CircleCheckBox(checked: kotlin.Boolean, onCheckedChange: kotlin.Function1, modifier: androidx.compose.ui.Modifier): kotlin.Unit skippable: true @@ -193,12 +145,6 @@ public fun com.ninecraft.booket.core.designsystem.component.checkbox.CircleCheck - onCheckedChange: STABLE (function type) - modifier: STABLE (marked @Stable or @Immutable) -@Composable -private fun com.ninecraft.booket.core.designsystem.component.checkbox.CircleCheckboxPreview(): kotlin.Unit - skippable: true - restartable: true - params: - @Composable public fun com.ninecraft.booket.core.designsystem.component.checkbox.SquareCheckBox(checked: kotlin.Boolean, onCheckedChange: kotlin.Function1, modifier: androidx.compose.ui.Modifier): kotlin.Unit skippable: true @@ -208,12 +154,6 @@ public fun com.ninecraft.booket.core.designsystem.component.checkbox.SquareCheck - onCheckedChange: STABLE (function type) - modifier: STABLE (marked @Stable or @Immutable) -@Composable -private fun com.ninecraft.booket.core.designsystem.component.checkbox.SquareCheckboxPreview(): kotlin.Unit - skippable: true - restartable: true - params: - @Composable public fun com.ninecraft.booket.core.designsystem.component.checkbox.TickOnlyCheckBox(checked: kotlin.Boolean, onCheckedChange: kotlin.Function1, modifier: androidx.compose.ui.Modifier): kotlin.Unit skippable: true @@ -223,12 +163,6 @@ public fun com.ninecraft.booket.core.designsystem.component.checkbox.TickOnlyChe - onCheckedChange: STABLE (function type) - modifier: STABLE (marked @Stable or @Immutable) -@Composable -private fun com.ninecraft.booket.core.designsystem.component.checkbox.TickOnlyCheckBoxPreview(): kotlin.Unit - skippable: true - restartable: true - params: - @Composable public fun com.ninecraft.booket.core.designsystem.component.textfield.ReedRecordTextField(recordState: androidx.compose.foundation.text.input.TextFieldState, recordHintRes: kotlin.Int, modifier: androidx.compose.ui.Modifier, inputTransformation: androidx.compose.foundation.text.input.InputTransformation?, keyboardOptions: androidx.compose.foundation.text.KeyboardOptions, lineLimits: androidx.compose.foundation.text.input.TextFieldLineLimits, isError: kotlin.Boolean, errorMessage: kotlin.String, onClear: kotlin.Function0?, onNext: kotlin.Function0, backgroundColor: androidx.compose.ui.graphics.Color, textColor: androidx.compose.ui.graphics.Color, cornerShape: androidx.compose.foundation.shape.RoundedCornerShape, borderStroke: androidx.compose.foundation.BorderStroke): kotlin.Unit skippable: true @@ -249,12 +183,6 @@ public fun com.ninecraft.booket.core.designsystem.component.textfield.ReedRecord - cornerShape: STABLE (known stable type) - borderStroke: STABLE (marked @Stable or @Immutable) -@Composable -private fun com.ninecraft.booket.core.designsystem.component.textfield.ReedRecordTextFieldPreview(): kotlin.Unit - skippable: true - restartable: true - params: - @Composable public fun com.ninecraft.booket.core.designsystem.component.textfield.ReedTextField(queryState: androidx.compose.foundation.text.input.TextFieldState, queryHintRes: kotlin.Int, onSearch: kotlin.Function1, onClear: kotlin.Function0, modifier: androidx.compose.ui.Modifier, backgroundColor: androidx.compose.ui.graphics.Color, textColor: androidx.compose.ui.graphics.Color, cornerShape: androidx.compose.foundation.shape.RoundedCornerShape, borderStroke: androidx.compose.foundation.BorderStroke?, searchIconTint: androidx.compose.ui.graphics.Color): kotlin.Unit skippable: true @@ -271,12 +199,6 @@ public fun com.ninecraft.booket.core.designsystem.component.textfield.ReedTextFi - borderStroke: STABLE (marked @Stable or @Immutable) - searchIconTint: STABLE (marked @Stable or @Immutable) -@Composable -private fun com.ninecraft.booket.core.designsystem.component.textfield.ReedTextFieldPreview(): kotlin.Unit - skippable: true - restartable: true - params: - @Composable public fun com.ninecraft.booket.core.designsystem.theme.ReedTheme(content: @[Composable] androidx.compose.runtime.internal.ComposableFunction0): kotlin.Unit skippable: true diff --git a/core/di/build.gradle.kts b/core/di/build.gradle.kts new file mode 100644 index 00000000..2755c345 --- /dev/null +++ b/core/di/build.gradle.kts @@ -0,0 +1,10 @@ +@file:Suppress("INLINE_FROM_HIGHER_PLATFORM") + +plugins { + alias(libs.plugins.booket.android.library) + alias(libs.plugins.metro) +} + +android { + namespace = "com.ninecraft.booket.core.di" +} diff --git a/core/di/src/main/kotlin/com/ninecraft/booket/core/di/ActivityKey.kt b/core/di/src/main/kotlin/com/ninecraft/booket/core/di/ActivityKey.kt new file mode 100644 index 00000000..96744840 --- /dev/null +++ b/core/di/src/main/kotlin/com/ninecraft/booket/core/di/ActivityKey.kt @@ -0,0 +1,10 @@ +package com.ninecraft.booket.core.di + +import android.app.Activity +import dev.zacsweers.metro.MapKey +import kotlin.reflect.KClass + +/** A [MapKey] annotation for binding Activities in a multibinding map. */ +@MapKey +@Target(AnnotationTarget.CLASS) +annotation class ActivityKey(val value: KClass) diff --git a/core/di/src/main/kotlin/com/ninecraft/booket/core/di/ApplicationContext.kt b/core/di/src/main/kotlin/com/ninecraft/booket/core/di/ApplicationContext.kt new file mode 100644 index 00000000..fd8504b4 --- /dev/null +++ b/core/di/src/main/kotlin/com/ninecraft/booket/core/di/ApplicationContext.kt @@ -0,0 +1,6 @@ +package com.ninecraft.booket.core.di + +import dev.zacsweers.metro.Qualifier + +@Qualifier +annotation class ApplicationContext diff --git a/core/di/src/main/kotlin/com/ninecraft/booket/core/di/DataScope.kt b/core/di/src/main/kotlin/com/ninecraft/booket/core/di/DataScope.kt new file mode 100644 index 00000000..4075a9c7 --- /dev/null +++ b/core/di/src/main/kotlin/com/ninecraft/booket/core/di/DataScope.kt @@ -0,0 +1,9 @@ +package com.ninecraft.booket.core.di + +/** + * Scope for data layer dependencies including: + * - Network (Retrofit, OkHttp, Interceptors) + * - DataStore and DataSources + * - Repository implementations + */ +abstract class DataScope private constructor() diff --git a/core/di/src/main/kotlin/com/ninecraft/booket/core/di/ServiceKey.kt b/core/di/src/main/kotlin/com/ninecraft/booket/core/di/ServiceKey.kt new file mode 100644 index 00000000..fd518530 --- /dev/null +++ b/core/di/src/main/kotlin/com/ninecraft/booket/core/di/ServiceKey.kt @@ -0,0 +1,9 @@ +package com.ninecraft.booket.core.di + +import android.app.Service +import dev.zacsweers.metro.MapKey +import kotlin.reflect.KClass + +@MapKey +@Target(AnnotationTarget.CLASS) +annotation class ServiceKey(val value: KClass) diff --git a/core/network/build.gradle.kts b/core/network/build.gradle.kts index 5d03bc71..8dc45dd0 100644 --- a/core/network/build.gradle.kts +++ b/core/network/build.gradle.kts @@ -6,7 +6,7 @@ import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties plugins { alias(libs.plugins.booket.android.library) alias(libs.plugins.booket.android.retrofit) - alias(libs.plugins.booket.android.hilt) + alias(libs.plugins.metro) } android { @@ -30,7 +30,9 @@ android { dependencies { implementations( projects.core.datastore.api, + projects.core.di, + libs.kotlinx.coroutines.core, libs.logger, ) } diff --git a/core/network/src/main/kotlin/com/ninecraft/booket/core/network/TokenAuthenticator.kt b/core/network/src/main/kotlin/com/ninecraft/booket/core/network/TokenAuthenticator.kt index 011ab824..ca7104cc 100644 --- a/core/network/src/main/kotlin/com/ninecraft/booket/core/network/TokenAuthenticator.kt +++ b/core/network/src/main/kotlin/com/ninecraft/booket/core/network/TokenAuthenticator.kt @@ -1,18 +1,22 @@ package com.ninecraft.booket.core.network import com.ninecraft.booket.core.datastore.api.datasource.TokenDataSource +import com.ninecraft.booket.core.di.DataScope import com.ninecraft.booket.core.network.request.RefreshTokenRequest import com.ninecraft.booket.core.network.service.ReedService import com.orhanobut.logger.Logger +import dev.zacsweers.metro.Inject +import dev.zacsweers.metro.Provider +import dev.zacsweers.metro.SingleIn import kotlinx.coroutines.runBlocking import okhttp3.Authenticator import okhttp3.Request import okhttp3.Response import okhttp3.Route -import javax.inject.Inject -import javax.inject.Provider -class TokenAuthenticator @Inject constructor( +@SingleIn(DataScope::class) +@Inject +class TokenAuthenticator( private val tokenDataSource: TokenDataSource, private val serviceProvider: Provider, ) : Authenticator { @@ -28,7 +32,7 @@ class TokenAuthenticator @Inject constructor( } val refreshTokenRequest = RefreshTokenRequest(refreshToken) - val refreshResponse = serviceProvider.get().refreshToken(refreshTokenRequest) + val refreshResponse = serviceProvider().refreshToken(refreshTokenRequest) tokenDataSource.apply { setAccessToken(refreshResponse.accessToken) diff --git a/core/network/src/main/kotlin/com/ninecraft/booket/core/network/TokenInterceptor.kt b/core/network/src/main/kotlin/com/ninecraft/booket/core/network/TokenInterceptor.kt index a49e6fe8..daa2d701 100644 --- a/core/network/src/main/kotlin/com/ninecraft/booket/core/network/TokenInterceptor.kt +++ b/core/network/src/main/kotlin/com/ninecraft/booket/core/network/TokenInterceptor.kt @@ -1,12 +1,16 @@ package com.ninecraft.booket.core.network import com.ninecraft.booket.core.datastore.api.datasource.TokenDataSource +import dev.zacsweers.metro.Inject +import com.ninecraft.booket.core.di.DataScope +import dev.zacsweers.metro.SingleIn import kotlinx.coroutines.runBlocking import okhttp3.Interceptor import okhttp3.Response -import javax.inject.Inject -internal class TokenInterceptor @Inject constructor( +@SingleIn(DataScope::class) +@Inject +class TokenInterceptor( private val tokenDataSource: TokenDataSource, ) : Interceptor { diff --git a/core/network/src/main/kotlin/com/ninecraft/booket/core/network/di/NetworkModule.kt b/core/network/src/main/kotlin/com/ninecraft/booket/core/network/di/NetworkGraph.kt similarity index 69% rename from core/network/src/main/kotlin/com/ninecraft/booket/core/network/di/NetworkModule.kt rename to core/network/src/main/kotlin/com/ninecraft/booket/core/network/di/NetworkGraph.kt index 572a0684..d0fb0cf4 100644 --- a/core/network/src/main/kotlin/com/ninecraft/booket/core/network/di/NetworkModule.kt +++ b/core/network/src/main/kotlin/com/ninecraft/booket/core/network/di/NetworkGraph.kt @@ -1,36 +1,30 @@ package com.ninecraft.booket.core.network.di import android.util.Log -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent +import com.ninecraft.booket.core.di.DataScope +import com.ninecraft.booket.core.network.BuildConfig +import com.ninecraft.booket.core.network.TokenAuthenticator +import com.ninecraft.booket.core.network.TokenInterceptor +import com.ninecraft.booket.core.network.service.ReedService +import com.orhanobut.logger.AndroidLogAdapter +import com.orhanobut.logger.PrettyFormatStrategy +import dev.zacsweers.metro.ContributesTo +import dev.zacsweers.metro.Provides import kotlinx.serialization.json.Json import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.converter.kotlinx.serialization.asConverterFactory -import com.ninecraft.booket.core.network.BuildConfig -import com.ninecraft.booket.core.network.TokenInterceptor -import com.ninecraft.booket.core.network.TokenAuthenticator -import com.ninecraft.booket.core.network.service.ReedService -import com.orhanobut.logger.AndroidLogAdapter -import com.orhanobut.logger.PrettyFormatStrategy import retrofit2.create import java.util.concurrent.TimeUnit -import javax.inject.Singleton private const val MaxTimeoutMillis = 15_000L private val jsonRule = Json { - // 기본값도 JSON에 포함하여 직렬화 encodeDefaults = true - // JSON에 정의되지 않은 키는 무시 (역직렬화 시 에러 방지) ignoreUnknownKeys = true - // JSON을 보기 좋게 들여쓰기하여 포맷팅 prettyPrint = true - // 엄격하지 않은 파싱 (따옴표 없는 키, 후행 쉼표 등 허용) isLenient = true } @@ -50,29 +44,26 @@ private val FILTERED_HEADERS = setOf( "content-length", ) -@Module -@InstallIn(SingletonComponent::class) -internal object NetworkModule { +@ContributesTo(DataScope::class) +interface NetworkGraph { - @Singleton @Provides - internal fun provideNetworkLogAdapter(): AndroidLogAdapter { + fun provideNetworkLogAdapter(): AndroidLogAdapter { val networkFormatStrategy = PrettyFormatStrategy.newBuilder() - .showThreadInfo(false) // 스레드 정보 제거 - .methodCount(0) // 메서드 스택 제거 - .methodOffset(0) // 오프셋 제거 - .tag("NETWORK") // API 호출 전용 태그 + .showThreadInfo(false) + .methodCount(0) + .methodOffset(0) + .tag("NETWORK") .build() return AndroidLogAdapter(networkFormatStrategy) } - @Singleton @Provides - internal fun provideHttpLoggingInterceptor( + fun provideHttpLoggingInterceptor( networkLogAdapter: AndroidLogAdapter, ): HttpLoggingInterceptor { - return HttpLoggingInterceptor { message -> + val interceptor = HttpLoggingInterceptor { message -> val shouldFilter = FILTERED_HEADERS.any { header -> message.lowercase().contains("$header:") } @@ -83,18 +74,17 @@ internal object NetworkModule { if (!shouldFilter && !isDuplicateContentType && message.isNotBlank()) { networkLogAdapter.log(Log.DEBUG, null, message) } - }.apply { - level = if (BuildConfig.DEBUG) { - HttpLoggingInterceptor.Level.BODY - } else { - HttpLoggingInterceptor.Level.NONE - } } + interceptor.level = if (BuildConfig.DEBUG) { + HttpLoggingInterceptor.Level.BODY + } else { + HttpLoggingInterceptor.Level.NONE + } + return interceptor } - @Singleton @Provides - internal fun provideOkHttpClient( + fun provideOkHttpClient( httpLoggingInterceptor: HttpLoggingInterceptor, tokenInterceptor: TokenInterceptor, tokenAuthenticator: TokenAuthenticator, @@ -109,9 +99,8 @@ internal object NetworkModule { .build() } - @Singleton @Provides - internal fun provideRetrofit( + fun provideRetrofit( okHttpClient: OkHttpClient, ): Retrofit { return Retrofit.Builder() @@ -121,9 +110,8 @@ internal object NetworkModule { .build() } - @Singleton @Provides - internal fun provideReedService( + fun provideReedService( retrofit: Retrofit, ): ReedService { return retrofit.create() diff --git a/core/ocr/build.gradle.kts b/core/ocr/build.gradle.kts index 8fcb5005..fc89475b 100644 --- a/core/ocr/build.gradle.kts +++ b/core/ocr/build.gradle.kts @@ -6,7 +6,7 @@ import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties plugins { alias(libs.plugins.booket.android.library) alias(libs.plugins.booket.android.retrofit) - alias(libs.plugins.booket.android.hilt) + alias(libs.plugins.metro) } android { @@ -24,7 +24,9 @@ android { dependencies { implementations( projects.core.common, + projects.core.di, + libs.kotlinx.coroutines.core, libs.logger, ) } diff --git a/core/ocr/src/main/kotlin/com/ninecraft/booket/core/ocr/di/CloudVisionNetworkQualifier.kt b/core/ocr/src/main/kotlin/com/ninecraft/booket/core/ocr/di/CloudVisionNetworkQualifier.kt index b79d4ea1..ec0b0b17 100644 --- a/core/ocr/src/main/kotlin/com/ninecraft/booket/core/ocr/di/CloudVisionNetworkQualifier.kt +++ b/core/ocr/src/main/kotlin/com/ninecraft/booket/core/ocr/di/CloudVisionNetworkQualifier.kt @@ -1,6 +1,6 @@ package com.ninecraft.booket.core.ocr.di -import javax.inject.Qualifier +import dev.zacsweers.metro.Qualifier @Qualifier @Retention(AnnotationRetention.BINARY) diff --git a/core/ocr/src/main/kotlin/com/ninecraft/booket/core/ocr/di/CloudVisionNetworkModule.kt b/core/ocr/src/main/kotlin/com/ninecraft/booket/core/ocr/di/OcrGraph.kt similarity index 65% rename from core/ocr/src/main/kotlin/com/ninecraft/booket/core/ocr/di/CloudVisionNetworkModule.kt rename to core/ocr/src/main/kotlin/com/ninecraft/booket/core/ocr/di/OcrGraph.kt index c38717b5..d21eb0ac 100644 --- a/core/ocr/src/main/kotlin/com/ninecraft/booket/core/ocr/di/CloudVisionNetworkModule.kt +++ b/core/ocr/src/main/kotlin/com/ninecraft/booket/core/ocr/di/OcrGraph.kt @@ -1,44 +1,37 @@ package com.ninecraft.booket.core.ocr.di +import com.ninecraft.booket.core.di.DataScope import com.ninecraft.booket.core.ocr.BuildConfig import com.ninecraft.booket.core.ocr.service.CloudVisionService -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent +import dev.zacsweers.metro.ContributesTo +import dev.zacsweers.metro.Provides import kotlinx.serialization.json.Json import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.converter.kotlinx.serialization.asConverterFactory +import retrofit2.create import java.util.concurrent.TimeUnit -import javax.inject.Singleton -private const val BASE_URL = "https://vision.googleapis.com/" private const val MaxTimeoutMillis = 15_000L +private const val CLOUD_VISION_BASE_URL = "https://vision.googleapis.com/" private val jsonRule = Json { - // 기본값도 JSON에 포함하여 직렬화 encodeDefaults = true - // JSON에 정의되지 않은 키는 무시 (역직렬화 시 에러 방지) ignoreUnknownKeys = true - // JSON을 보기 좋게 들여쓰기하여 포맷팅 prettyPrint = true - // 엄격하지 않은 파싱 (따옴표 없는 키, 후행 쉼표 등 허용) isLenient = true } private val jsonConverterFactory = jsonRule.asConverterFactory("application/json".toMediaType()) -@Module -@InstallIn(SingletonComponent::class) -object CloudVisionNetworkModule { +@ContributesTo(DataScope::class) +interface OcrGraph { - @Provides - @Singleton @CloudVisionOkHttp - fun provideOkHttp(): OkHttpClient { + @Provides + fun provideCloudVisionOkHttpClient(): OkHttpClient { val log = HttpLoggingInterceptor().apply { redactHeader("X-Goog-Api-Key") level = if (BuildConfig.DEBUG) { @@ -55,21 +48,22 @@ object CloudVisionNetworkModule { .build() } - @Provides - @Singleton @CloudVisionRetrofit - fun provideRetrofit( + @Provides + fun provideCloudVisionRetrofit( @CloudVisionOkHttp okHttpClient: OkHttpClient, ): Retrofit { return Retrofit.Builder() - .baseUrl(BASE_URL) + .baseUrl(CLOUD_VISION_BASE_URL) .client(okHttpClient) .addConverterFactory(jsonConverterFactory) .build() } @Provides - @Singleton - fun provideVisionApi(@CloudVisionRetrofit retrofit: Retrofit): CloudVisionService = - retrofit.create(CloudVisionService::class.java) + fun provideCloudVisionService( + @CloudVisionRetrofit retrofit: Retrofit, + ): CloudVisionService { + return retrofit.create() + } } diff --git a/core/ocr/src/main/kotlin/com/ninecraft/booket/core/ocr/recognizer/CloudOcrRecognizer.kt b/core/ocr/src/main/kotlin/com/ninecraft/booket/core/ocr/recognizer/CloudOcrRecognizer.kt index 9035c5cf..fb39d0fc 100644 --- a/core/ocr/src/main/kotlin/com/ninecraft/booket/core/ocr/recognizer/CloudOcrRecognizer.kt +++ b/core/ocr/src/main/kotlin/com/ninecraft/booket/core/ocr/recognizer/CloudOcrRecognizer.kt @@ -14,9 +14,13 @@ import com.ninecraft.booket.core.ocr.service.CloudVisionService import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.io.File -import javax.inject.Inject +import dev.zacsweers.metro.Inject +import dev.zacsweers.metro.SingleIn +import com.ninecraft.booket.core.di.DataScope -class CloudOcrRecognizer @Inject constructor( +@SingleIn(DataScope::class) +@Inject +class CloudOcrRecognizer( private val service: CloudVisionService, ) { suspend fun recognizeText(imageUri: Uri): Result = runSuspendCatching { diff --git a/core/ui/stability/ui.stability b/core/ui/stability/ui.stability index 44d5e658..964ce26c 100644 --- a/core/ui/stability/ui.stability +++ b/core/ui/stability/ui.stability @@ -35,12 +35,6 @@ public fun com.ninecraft.booket.core.ui.component.InfinityLazyColumn(modifier: a - loadMore: STABLE (function type) - content: STABLE (function type) -@Composable -private fun com.ninecraft.booket.core.ui.component.InfinityLazyColumnPreview(): kotlin.Unit - skippable: true - restartable: true - params: - @Composable public fun com.ninecraft.booket.core.ui.component.LoadStateFooter(footerState: com.ninecraft.booket.core.ui.component.FooterState, onRetryClick: kotlin.Function0, modifier: androidx.compose.ui.Modifier): kotlin.Unit skippable: true @@ -60,12 +54,6 @@ public fun com.ninecraft.booket.core.ui.component.ReedBackTopAppBar(modifier: an - title: STABLE (String is immutable) - onBackClick: STABLE (function type) -@Composable -private fun com.ninecraft.booket.core.ui.component.ReedBackTopAppBarPreview(): kotlin.Unit - skippable: true - restartable: true - params: - @Composable public fun com.ninecraft.booket.core.ui.component.ReedBottomSheet(onDismissRequest: kotlin.Function0, modifier: androidx.compose.ui.Modifier, sheetState: androidx.compose.material3.SheetState, content: @[Composable] androidx.compose.runtime.internal.ComposableFunction0): kotlin.Unit skippable: true @@ -82,12 +70,6 @@ private fun com.ninecraft.booket.core.ui.component.ReedBottomSheetPreview(): kot restartable: true params: -@Composable -private fun com.ninecraft.booket.core.ui.component.ReedChoiceDialogPreview(): kotlin.Unit - skippable: true - restartable: true - params: - @Composable public fun com.ninecraft.booket.core.ui.component.ReedCloseTopAppBar(modifier: androidx.compose.ui.Modifier, isDark: kotlin.Boolean, title: kotlin.String, onClose: kotlin.Function0): kotlin.Unit skippable: true @@ -98,18 +80,6 @@ public fun com.ninecraft.booket.core.ui.component.ReedCloseTopAppBar(modifier: a - title: STABLE (String is immutable) - onClose: STABLE (function type) -@Composable -private fun com.ninecraft.booket.core.ui.component.ReedCloseTopAppBarPreview(): kotlin.Unit - skippable: true - restartable: true - params: - -@Composable -private fun com.ninecraft.booket.core.ui.component.ReedConfirmDialogPreview(): kotlin.Unit - skippable: true - restartable: true - params: - @Composable public fun com.ninecraft.booket.core.ui.component.ReedDialog(confirmButtonText: kotlin.String, onConfirmRequest: kotlin.Function0, modifier: androidx.compose.ui.Modifier, title: kotlin.String?, description: kotlin.String?, dismissButtonText: kotlin.String?, onDismissRequest: kotlin.Function0, headerContent: @[Composable] androidx.compose.runtime.internal.ComposableFunction0?): kotlin.Unit skippable: true @@ -118,9 +88,9 @@ public fun com.ninecraft.booket.core.ui.component.ReedDialog(confirmButtonText: - confirmButtonText: STABLE (String is immutable) - onConfirmRequest: STABLE (function type) - modifier: STABLE (marked @Stable or @Immutable) - - title: STABLE - - description: STABLE - - dismissButtonText: STABLE + - title: STABLE (class with no mutable properties) + - description: STABLE (class with no mutable properties) + - dismissButtonText: STABLE (class with no mutable properties) - onDismissRequest: STABLE (function type) - headerContent: STABLE (composable function type) @@ -175,19 +145,13 @@ public fun com.ninecraft.booket.core.ui.component.ReedTopAppBar(modifier: androi - modifier: STABLE (marked @Stable or @Immutable) - isDark: STABLE (primitive type) - title: STABLE (String is immutable) - - startIconRes: STABLE + - startIconRes: STABLE (class with no mutable properties) - startIconDescription: STABLE (String is immutable) - startIconOnClick: STABLE (function type) - - endIconRes: STABLE + - endIconRes: STABLE (class with no mutable properties) - endIconDescription: STABLE (String is immutable) - endIconOnClick: STABLE (function type) -@Composable -private fun com.ninecraft.booket.core.ui.component.ReedTopAppBarPreview(): kotlin.Unit - skippable: true - restartable: true - params: - @Composable private fun com.ninecraft.booket.core.ui.component.onLoadMore(limitCount: kotlin.Int, loadOnBottom: kotlin.Boolean, action: kotlin.Function0): kotlin.Unit skippable: true diff --git a/feature/detail/build.gradle.kts b/feature/detail/build.gradle.kts index c3af32c6..de465599 100644 --- a/feature/detail/build.gradle.kts +++ b/feature/detail/build.gradle.kts @@ -10,10 +10,6 @@ android { namespace = "com.ninecraft.booket.feature.detail" } -ksp { - arg("circuit.codegen.mode", "hilt") -} - dependencies { implementations( libs.kotlinx.collections.immutable, diff --git a/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt b/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt index 07833fe8..c49bd0d5 100644 --- a/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt +++ b/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt @@ -29,10 +29,10 @@ import com.slack.circuit.retained.rememberRetained import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.presenter.Presenter import com.slack.circuitx.effects.ImpressionEffect -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedFactory +import dev.zacsweers.metro.AssistedInject import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList @@ -43,13 +43,21 @@ import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch import java.time.LocalDateTime -class BookDetailPresenter @AssistedInject constructor( +@AssistedInject +class BookDetailPresenter( @Assisted private val screen: BookDetailScreen, @Assisted private val navigator: Navigator, private val bookRepository: BookRepository, private val recordRepository: RecordRepository, private val analyticsHelper: AnalyticsHelper, ) : Presenter { + + @CircuitInject(BookDetailScreen::class, AppScope::class) + @AssistedFactory + fun interface Factory { + fun create(screen: BookDetailScreen, navigator: Navigator): BookDetailPresenter + } + companion object { private const val PAGE_SIZE = 20 private const val START_INDEX = 0 @@ -418,13 +426,4 @@ class BookDetailPresenter @AssistedInject constructor( eventSink = ::handleEvent, ) } - - @CircuitInject(BookDetailScreen::class, ActivityRetainedComponent::class) - @AssistedFactory - fun interface Factory { - fun create( - screen: BookDetailScreen, - navigator: Navigator, - ): BookDetailPresenter - } } diff --git a/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUi.kt b/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUi.kt index 6ce7d6f7..2c7ce7f6 100644 --- a/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUi.kt +++ b/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUi.kt @@ -56,14 +56,14 @@ import com.ninecraft.booket.feature.detail.record.component.RecordMenuBottomShee import com.ninecraft.booket.feature.screens.BookDetailScreen import com.skydoves.compose.stability.runtime.TraceRecomposition import com.slack.circuit.codegen.annotations.CircuitInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.launch import com.ninecraft.booket.core.designsystem.R as designR @TraceRecomposition @OptIn(ExperimentalMaterial3Api::class) -@CircuitInject(BookDetailScreen::class, ActivityRetainedComponent::class) +@CircuitInject(BookDetailScreen::class, AppScope::class) @Composable internal fun BookDetailUi( state: BookDetailUiState, diff --git a/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/card/RecordCardPresenter.kt b/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/card/RecordCardPresenter.kt index 1ab1de37..c580344b 100644 --- a/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/card/RecordCardPresenter.kt +++ b/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/card/RecordCardPresenter.kt @@ -11,17 +11,24 @@ import com.slack.circuit.retained.rememberRetained import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.presenter.Presenter import com.slack.circuitx.effects.ImpressionEffect -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedFactory +import dev.zacsweers.metro.AssistedInject -class RecordCardPresenter @AssistedInject constructor( +@AssistedInject +class RecordCardPresenter( @Assisted private val screen: RecordCardScreen, @Assisted private val navigator: Navigator, private val analyticsHelper: AnalyticsHelper, ) : Presenter { + @CircuitInject(RecordCardScreen::class, AppScope::class) + @AssistedFactory + fun interface Factory { + fun create(screen: RecordCardScreen, navigator: Navigator): RecordCardPresenter + } + companion object { private const val RECORD_CARD_SAVE = "record_card_save" private const val RECORD_CARD_SHARE = "record_card_share" @@ -82,12 +89,3 @@ class RecordCardPresenter @AssistedInject constructor( ) } } - -@CircuitInject(RecordCardScreen::class, ActivityRetainedComponent::class) -@AssistedFactory -fun interface Factory { - fun create( - screen: RecordCardScreen, - navigator: Navigator, - ): RecordCardPresenter -} diff --git a/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/card/RecordCardUi.kt b/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/card/RecordCardUi.kt index 3e3813be..9c75111c 100644 --- a/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/card/RecordCardUi.kt +++ b/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/card/RecordCardUi.kt @@ -38,11 +38,11 @@ import com.ninecraft.booket.feature.detail.card.component.RecordCard import com.ninecraft.booket.feature.screens.RecordCardScreen import com.skydoves.compose.stability.runtime.TraceRecomposition import com.slack.circuit.codegen.annotations.CircuitInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope import com.ninecraft.booket.core.designsystem.R as designR @TraceRecomposition -@CircuitInject(RecordCardScreen::class, ActivityRetainedComponent::class) +@CircuitInject(RecordCardScreen::class, AppScope::class) @Composable internal fun RecordCardUi( state: RecordCardUiState, diff --git a/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt b/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt index fbc0723d..9f38f7a7 100644 --- a/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt +++ b/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt @@ -21,19 +21,26 @@ import com.slack.circuit.retained.rememberRetained import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.presenter.Presenter import com.slack.circuitx.effects.ImpressionEffect -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedFactory +import dev.zacsweers.metro.AssistedInject import kotlinx.coroutines.launch -class RecordDetailPresenter @AssistedInject constructor( +@AssistedInject +class RecordDetailPresenter( @Assisted private val screen: RecordDetailScreen, @Assisted private val navigator: Navigator, private val repository: RecordRepository, private val analyticsHelper: AnalyticsHelper, ) : Presenter { + @CircuitInject(RecordDetailScreen::class, AppScope::class) + @AssistedFactory + fun interface Factory { + fun create(screen: RecordDetailScreen, navigator: Navigator): RecordDetailPresenter + } + companion object { private const val RECORD_DELETE = "record_delete" private const val RECORD_DELETE_COMPLETE = "record_delete_complete" @@ -186,13 +193,4 @@ class RecordDetailPresenter @AssistedInject constructor( eventSink = ::handleEvent, ) } - - @CircuitInject(RecordDetailScreen::class, ActivityRetainedComponent::class) - @AssistedFactory - fun interface Factory { - fun create( - screen: RecordDetailScreen, - navigator: Navigator, - ): RecordDetailPresenter - } } diff --git a/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.kt b/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.kt index 8bedd731..3f3de806 100644 --- a/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.kt +++ b/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.kt @@ -33,13 +33,13 @@ import com.ninecraft.booket.feature.detail.record.component.ReviewItem import com.ninecraft.booket.feature.screens.RecordDetailScreen import com.skydoves.compose.stability.runtime.TraceRecomposition import com.slack.circuit.codegen.annotations.CircuitInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope import kotlinx.coroutines.launch import com.ninecraft.booket.core.designsystem.R as designR @TraceRecomposition @OptIn(ExperimentalMaterial3Api::class) -@CircuitInject(RecordDetailScreen::class, ActivityRetainedComponent::class) +@CircuitInject(RecordDetailScreen::class, AppScope::class) @Composable internal fun RecordDetailUi( state: RecordDetailUiState, diff --git a/feature/detail/stability/detail.stability b/feature/detail/stability/detail.stability index 7b658259..4866c078 100644 --- a/feature/detail/stability/detail.stability +++ b/feature/detail/stability/detail.stability @@ -9,7 +9,7 @@ internal fun com.ninecraft.booket.feature.detail.book.BookDetailContent(state: c skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - innerPadding: STABLE (marked @Stable or @Immutable) - modifier: STABLE (marked @Stable or @Immutable) - lazyListState: STABLE (marked @Stable or @Immutable) @@ -31,7 +31,7 @@ internal fun com.ninecraft.booket.feature.detail.book.BookDetailUi(state: com.ni skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - modifier: STABLE (marked @Stable or @Immutable) @Composable @@ -39,7 +39,7 @@ internal fun com.ninecraft.booket.feature.detail.book.HandleBookDetailSideEffect skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - eventSink: STABLE (function type) @Composable @@ -61,7 +61,7 @@ public fun com.ninecraft.booket.feature.detail.book.component.BookStatusItem(ite skippable: true restartable: true params: - - item: STABLE + - item: STABLE (class with no mutable properties) - selected: STABLE (primitive type) - onClick: STABLE (function type) - modifier: STABLE (marked @Stable or @Immutable) @@ -75,8 +75,8 @@ internal fun com.ninecraft.booket.feature.detail.book.component.BookUpdateBottom - sheetState: STABLE (marked @Stable or @Immutable) - onCloseButtonClick: STABLE (function type) - bookStatuses: STABLE (known stable type) - - currentBookStatus: STABLE - - selectedBookStatus: STABLE + - currentBookStatus: STABLE (class with no mutable properties) + - selectedBookStatus: STABLE (class with no mutable properties) - onItemSelected: STABLE (function type) - onBookUpdateButtonClick: STABLE (function type) - modifier: STABLE (marked @Stable or @Immutable) @@ -152,7 +152,7 @@ internal fun com.ninecraft.booket.feature.detail.book.component.ReadingRecordsHe restartable: true params: - totalCount: STABLE (primitive type) - - currentRecordSort: STABLE + - currentRecordSort: STABLE (class with no mutable properties) - onReadingRecordClick: STABLE (function type) - modifier: STABLE (marked @Stable or @Immutable) @@ -186,7 +186,7 @@ internal fun com.ninecraft.booket.feature.detail.book.component.RecordSortBottom - sheetState: STABLE (marked @Stable or @Immutable) - onCloseButtonClick: STABLE (function type) - recordSortItems: STABLE (known stable type) - - currentRecordSort: STABLE + - currentRecordSort: STABLE (class with no mutable properties) - onItemSelected: STABLE (function type) - modifier: STABLE (marked @Stable or @Immutable) @@ -201,7 +201,7 @@ public fun com.ninecraft.booket.feature.detail.book.component.RecordSortItem(ite skippable: true restartable: true params: - - item: STABLE + - item: STABLE (class with no mutable properties) - selected: STABLE (primitive type) - onClick: STABLE (function type) - modifier: STABLE (marked @Stable or @Immutable) @@ -225,7 +225,7 @@ internal fun com.ninecraft.booket.feature.detail.card.HandleRecordCardSideEffect skippable: false restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - recordCardGraphicsLayer: UNSTABLE (has mutable properties or unstable members) - eventSink: STABLE (function type) @@ -240,7 +240,7 @@ internal fun com.ninecraft.booket.feature.detail.card.RecordCardUi(state: com.ni skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - modifier: STABLE (marked @Stable or @Immutable) @Composable @@ -250,13 +250,13 @@ private fun com.ninecraft.booket.feature.detail.card.RecordCardUiPreview(): kotl params: @Composable -internal fun com.ninecraft.booket.feature.detail.card.component.RecordCard(quote: kotlin.String, bookTitle: kotlin.String, emotionTag: kotlin.String, modifier: androidx.compose.ui.Modifier): kotlin.Unit +internal fun com.ninecraft.booket.feature.detail.card.component.RecordCard(quote: kotlin.String, bookTitle: kotlin.String, emotion: kotlin.String, modifier: androidx.compose.ui.Modifier): kotlin.Unit skippable: true restartable: true params: - quote: STABLE (String is immutable) - bookTitle: STABLE (String is immutable) - - emotionTag: STABLE (String is immutable) + - emotion: STABLE (String is immutable) - modifier: STABLE (marked @Stable or @Immutable) @Composable @@ -270,14 +270,14 @@ internal fun com.ninecraft.booket.feature.detail.record.HandleRecordDetailSideEf skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) @Composable private fun com.ninecraft.booket.feature.detail.record.RecordDetailContent(state: com.ninecraft.booket.feature.detail.record.RecordDetailUiState, modifier: androidx.compose.ui.Modifier): kotlin.Unit skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - modifier: STABLE (marked @Stable or @Immutable) @Composable @@ -291,7 +291,7 @@ internal fun com.ninecraft.booket.feature.detail.record.RecordDetailUi(state: co skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - modifier: STABLE (marked @Stable or @Immutable) @Composable diff --git a/feature/edit/build.gradle.kts b/feature/edit/build.gradle.kts index 092b4849..f22b6c1b 100644 --- a/feature/edit/build.gradle.kts +++ b/feature/edit/build.gradle.kts @@ -10,10 +10,6 @@ android { namespace = "com.ninecraft.booket.feature.edit" } -ksp { - arg("circuit.codegen.mode", "hilt") -} - dependencies { implementations( libs.kotlinx.collections.immutable, diff --git a/feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditPresenter.kt b/feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditPresenter.kt index cf132671..a5245b56 100644 --- a/feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditPresenter.kt +++ b/feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditPresenter.kt @@ -12,16 +12,24 @@ import com.slack.circuit.codegen.annotations.CircuitInject import com.slack.circuit.retained.rememberRetained import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.presenter.Presenter -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedFactory +import dev.zacsweers.metro.AssistedInject import kotlinx.collections.immutable.toPersistentList -class EmotionEditPresenter @AssistedInject constructor( +@AssistedInject +class EmotionEditPresenter( @Assisted private val screen: EmotionEditScreen, @Assisted private val navigator: Navigator, ) : Presenter { + + @CircuitInject(EmotionEditScreen::class, AppScope::class) + @AssistedFactory + fun interface Factory { + fun create(screen: EmotionEditScreen, navigator: Navigator): EmotionEditPresenter + } + @Composable override fun present(): EmotionEditUiState { var selectedEmotion by rememberRetained { mutableStateOf(screen.emotion) } @@ -56,12 +64,3 @@ class EmotionEditPresenter @AssistedInject constructor( ) } } - -@CircuitInject(EmotionEditScreen::class, ActivityRetainedComponent::class) -@AssistedFactory -fun interface Factory { - fun create( - screen: EmotionEditScreen, - navigator: Navigator, - ): EmotionEditPresenter -} diff --git a/feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditUi.kt b/feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditUi.kt index d425dba8..de02d041 100644 --- a/feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditUi.kt +++ b/feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditUi.kt @@ -39,11 +39,11 @@ import com.ninecraft.booket.feature.edit.R import com.ninecraft.booket.feature.screens.EmotionEditScreen import com.skydoves.compose.stability.runtime.TraceRecomposition import com.slack.circuit.codegen.annotations.CircuitInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope import kotlinx.collections.immutable.toPersistentList @TraceRecomposition -@CircuitInject(EmotionEditScreen::class, ActivityRetainedComponent::class) +@CircuitInject(EmotionEditScreen::class, AppScope::class) @Composable internal fun EmotionEditUi( state: EmotionEditUiState, diff --git a/feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/RecordEditPresenter.kt b/feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/RecordEditPresenter.kt index 0c1b7220..da4a22ad 100644 --- a/feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/RecordEditPresenter.kt +++ b/feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/RecordEditPresenter.kt @@ -22,19 +22,26 @@ import com.slack.circuit.retained.rememberRetained import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.presenter.Presenter import com.slack.circuitx.effects.ImpressionEffect -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedFactory +import dev.zacsweers.metro.AssistedInject import kotlinx.coroutines.launch -class RecordEditPresenter @AssistedInject constructor( +@AssistedInject +class RecordEditPresenter( @Assisted private val screen: RecordEditScreen, @Assisted private val navigator: Navigator, private val repository: RecordRepository, private val analyticsHelper: AnalyticsHelper, ) : Presenter { + @CircuitInject(RecordEditScreen::class, AppScope::class) + @AssistedFactory + fun interface Factory { + fun create(screen: RecordEditScreen, navigator: Navigator): RecordEditPresenter + } + companion object { private const val MAX_PAGE = 4032 private const val RECORD_EDIT = "record_edit_save" @@ -157,13 +164,4 @@ class RecordEditPresenter @AssistedInject constructor( eventSink = ::handleEvent, ) } - - @CircuitInject(RecordEditScreen::class, ActivityRetainedComponent::class) - @AssistedFactory - fun interface Factory { - fun create( - screen: RecordEditScreen, - navigator: Navigator, - ): RecordEditPresenter - } } diff --git a/feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/RecordEditUi.kt b/feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/RecordEditUi.kt index 84ee03c6..5225c343 100644 --- a/feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/RecordEditUi.kt +++ b/feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/RecordEditUi.kt @@ -47,11 +47,11 @@ import com.ninecraft.booket.feature.screens.RecordEditScreen import com.ninecraft.booket.feature.screens.arguments.RecordEditArgs import com.skydoves.compose.stability.runtime.TraceRecomposition import com.slack.circuit.codegen.annotations.CircuitInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope import com.ninecraft.booket.core.designsystem.R as designR @TraceRecomposition -@CircuitInject(RecordEditScreen::class, ActivityRetainedComponent::class) +@CircuitInject(RecordEditScreen::class, AppScope::class) @Composable internal fun RecordEditUi( state: RecordEditUiState, diff --git a/feature/edit/stability/edit.stability b/feature/edit/stability/edit.stability index c3591cda..b44dddd0 100644 --- a/feature/edit/stability/edit.stability +++ b/feature/edit/stability/edit.stability @@ -9,7 +9,7 @@ private fun com.ninecraft.booket.feature.edit.emotion.EmotionEditContent(state: skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - modifier: STABLE (marked @Stable or @Immutable) @Composable @@ -23,7 +23,7 @@ internal fun com.ninecraft.booket.feature.edit.emotion.EmotionEditUi(state: com. skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - modifier: STABLE (marked @Stable or @Immutable) @Composable @@ -33,11 +33,11 @@ private fun com.ninecraft.booket.feature.edit.emotion.EmotionEditUiPreview(): ko params: @Composable -private fun com.ninecraft.booket.feature.edit.emotion.EmotionItem(emotionTag: com.ninecraft.booket.core.designsystem.EmotionTag, onClick: kotlin.Function0, isSelected: kotlin.Boolean, modifier: androidx.compose.ui.Modifier): kotlin.Unit +private fun com.ninecraft.booket.feature.edit.emotion.EmotionItem(emotion: com.ninecraft.booket.core.model.Emotion, onClick: kotlin.Function0, isSelected: kotlin.Boolean, modifier: androidx.compose.ui.Modifier): kotlin.Unit skippable: true restartable: true params: - - emotionTag: STABLE + - emotion: STABLE (class with no mutable properties) - onClick: STABLE (function type) - isSelected: STABLE (primitive type) - modifier: STABLE (marked @Stable or @Immutable) @@ -47,14 +47,14 @@ internal fun com.ninecraft.booket.feature.edit.record.HandleRecordEditSideEffect skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) @Composable private fun com.ninecraft.booket.feature.edit.record.RecordEditContent(state: com.ninecraft.booket.feature.edit.record.RecordEditUiState): kotlin.Unit skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) @Composable public fun com.ninecraft.booket.feature.edit.record.RecordEditPresenter.present(): com.ninecraft.booket.feature.edit.record.RecordEditUiState @@ -67,7 +67,7 @@ internal fun com.ninecraft.booket.feature.edit.record.RecordEditUi(state: com.ni skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - modifier: STABLE (marked @Stable or @Immutable) @Composable diff --git a/feature/home/build.gradle.kts b/feature/home/build.gradle.kts index 840963c2..ebfdd6f2 100644 --- a/feature/home/build.gradle.kts +++ b/feature/home/build.gradle.kts @@ -10,10 +10,6 @@ android { namespace = "com.ninecraft.booket.feature.home" } -ksp { - arg("circuit.codegen.mode", "hilt") -} - dependencies { implementations( libs.logger, diff --git a/feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomePresenter.kt b/feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomePresenter.kt index 3ad31023..9b65a95f 100644 --- a/feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomePresenter.kt +++ b/feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomePresenter.kt @@ -27,15 +27,16 @@ import com.slack.circuit.retained.rememberRetained import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.presenter.Presenter import com.slack.circuitx.effects.ImpressionEffect -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedFactory +import dev.zacsweers.metro.AssistedInject import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.launch -class HomePresenter @AssistedInject constructor( +@AssistedInject +class HomePresenter( @Assisted private val navigator: Navigator, private val bookRepository: BookRepository, private val authRepository: AuthRepository, @@ -43,6 +44,12 @@ class HomePresenter @AssistedInject constructor( private val analyticsHelper: AnalyticsHelper, ) : Presenter { + @CircuitInject(HomeScreen::class, AppScope::class) + @AssistedFactory + fun interface Factory { + fun create(navigator: Navigator): HomePresenter + } + @Composable override fun present(): HomeUiState { val scope = rememberCoroutineScope() @@ -148,10 +155,4 @@ class HomePresenter @AssistedInject constructor( eventSink = ::handleEvent, ) } - - @CircuitInject(HomeScreen::class, ActivityRetainedComponent::class) - @AssistedFactory - fun interface Factory { - fun create(navigator: Navigator): HomePresenter - } } diff --git a/feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomeUi.kt b/feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomeUi.kt index f1858302..e912d033 100644 --- a/feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomeUi.kt +++ b/feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomeUi.kt @@ -49,11 +49,11 @@ import com.ninecraft.booket.feature.screens.component.MainBottomBar import com.ninecraft.booket.feature.screens.component.MainTab import com.skydoves.compose.stability.runtime.TraceRecomposition import com.slack.circuit.codegen.annotations.CircuitInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope import kotlinx.collections.immutable.toImmutableList @TraceRecomposition -@CircuitInject(HomeScreen::class, ActivityRetainedComponent::class) +@CircuitInject(HomeScreen::class, AppScope::class) @Composable internal fun HomeUi( state: HomeUiState, diff --git a/feature/home/stability/home.stability b/feature/home/stability/home.stability index c66b0bb9..d30e4f8b 100644 --- a/feature/home/stability/home.stability +++ b/feature/home/stability/home.stability @@ -9,14 +9,14 @@ internal fun com.ninecraft.booket.feature.home.HandleHomeSideEffects(state: com. skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) @Composable internal fun com.ninecraft.booket.feature.home.HomeContent(state: com.ninecraft.booket.feature.home.HomeUiState, modifier: androidx.compose.ui.Modifier): kotlin.Unit skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - modifier: STABLE (marked @Stable or @Immutable) @Composable @@ -36,7 +36,7 @@ internal fun com.ninecraft.booket.feature.home.HomeUi(state: com.ninecraft.booke skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - modifier: STABLE (marked @Stable or @Immutable) @Composable diff --git a/feature/library/build.gradle.kts b/feature/library/build.gradle.kts index 5f01e5ca..6c34df6e 100644 --- a/feature/library/build.gradle.kts +++ b/feature/library/build.gradle.kts @@ -10,10 +10,6 @@ android { namespace = "com.ninecraft.booket.feature.library" } -ksp { - arg("circuit.codegen.mode", "hilt") -} - dependencies { implementations( libs.logger, diff --git a/feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryPresenter.kt b/feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryPresenter.kt index e557f31c..54aebc08 100644 --- a/feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryPresenter.kt +++ b/feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryPresenter.kt @@ -28,20 +28,28 @@ import com.slack.circuit.retained.rememberRetained import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.presenter.Presenter import com.slack.circuitx.effects.ImpressionEffect -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedFactory +import dev.zacsweers.metro.AssistedInject import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.launch -class LibraryPresenter @AssistedInject constructor( +@AssistedInject +class LibraryPresenter( @Assisted private val navigator: Navigator, private val bookRepository: BookRepository, private val authRepository: AuthRepository, private val analyticsHelper: AnalyticsHelper, ) : Presenter { + + @CircuitInject(LibraryScreen::class, AppScope::class) + @AssistedFactory + fun interface Factory { + fun create(navigator: Navigator): LibraryPresenter + } + companion object { private const val PAGE_SIZE = 20 private const val START_INDEX = 0 @@ -214,10 +222,4 @@ class LibraryPresenter @AssistedInject constructor( eventSink = ::handleEvent, ) } - - @CircuitInject(LibraryScreen::class, ActivityRetainedComponent::class) - @AssistedFactory - fun interface Factory { - fun create(navigator: Navigator): LibraryPresenter - } } diff --git a/feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryUi.kt b/feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryUi.kt index aac44e11..137972ad 100644 --- a/feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryUi.kt +++ b/feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryUi.kt @@ -36,12 +36,12 @@ import com.ninecraft.booket.feature.screens.component.MainBottomBar import com.ninecraft.booket.feature.screens.component.MainTab import com.skydoves.compose.stability.runtime.TraceRecomposition import com.slack.circuit.codegen.annotations.CircuitInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList @TraceRecomposition -@CircuitInject(LibraryScreen::class, ActivityRetainedComponent::class) +@CircuitInject(LibraryScreen::class, AppScope::class) @Composable internal fun LibraryUi( state: LibraryUiState, diff --git a/feature/library/stability/library.stability b/feature/library/stability/library.stability index edab8a55..73e10888 100644 --- a/feature/library/stability/library.stability +++ b/feature/library/stability/library.stability @@ -15,7 +15,7 @@ internal fun com.ninecraft.booket.feature.library.HandleLibrarySideEffects(state skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - eventSink: STABLE (function type) @Composable @@ -23,7 +23,7 @@ internal fun com.ninecraft.booket.feature.library.LibraryContent(state: com.nine skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - modifier: STABLE (marked @Stable or @Immutable) @Composable @@ -43,7 +43,7 @@ internal fun com.ninecraft.booket.feature.library.LibraryUi(state: com.ninecraft skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - modifier: STABLE (marked @Stable or @Immutable) @Composable @@ -57,7 +57,7 @@ public fun com.ninecraft.booket.feature.library.component.FilterChip(option: com skippable: true restartable: true params: - - option: STABLE + - option: STABLE (class with no mutable properties) - count: STABLE (primitive type) - isSelected: STABLE (primitive type) - onChipClick: STABLE (function type) @@ -69,7 +69,7 @@ public fun com.ninecraft.booket.feature.library.component.FilterChipGroup(filter restartable: true params: - filterList: STABLE (known stable type) - - selectedChipOption: STABLE + - selectedChipOption: STABLE (class with no mutable properties) - onChipClick: STABLE (function type) - modifier: STABLE (marked @Stable or @Immutable) diff --git a/feature/login/build.gradle.kts b/feature/login/build.gradle.kts index cd173eeb..fa25f74d 100644 --- a/feature/login/build.gradle.kts +++ b/feature/login/build.gradle.kts @@ -10,10 +10,6 @@ android { namespace = "com.ninecraft.booket.feature.login" } -ksp { - arg("circuit.codegen.mode", "hilt") -} - dependencies { implementations( libs.logger, diff --git a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/KakaoLoginClient.kt b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/KakaoLoginClient.kt index 022b788c..6f1b7d22 100644 --- a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/KakaoLoginClient.kt +++ b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/KakaoLoginClient.kt @@ -5,10 +5,11 @@ import com.kakao.sdk.auth.model.OAuthToken import com.kakao.sdk.common.model.AuthError import com.kakao.sdk.user.UserApiClient import com.ninecraft.booket.core.designsystem.R as designR -import javax.inject.Inject +import dev.zacsweers.metro.Inject import com.orhanobut.logger.Logger -internal class KakaoLoginClient @Inject constructor() { +@Inject +internal class KakaoLoginClient { fun loginWithKakao( context: Context, onSuccess: (String) -> Unit, diff --git a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginPresenter.kt b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginPresenter.kt index 0bb0de32..6a717c49 100644 --- a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginPresenter.kt +++ b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginPresenter.kt @@ -20,13 +20,14 @@ import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.popUntil import com.slack.circuit.runtime.presenter.Presenter import com.slack.circuitx.effects.ImpressionEffect -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedFactory +import dev.zacsweers.metro.AssistedInject import kotlinx.coroutines.launch -class LoginPresenter @AssistedInject constructor( +@AssistedInject +class LoginPresenter( @Assisted private val screen: LoginScreen, @Assisted private val navigator: Navigator, private val authRepository: AuthRepository, @@ -34,6 +35,12 @@ class LoginPresenter @AssistedInject constructor( private val analyticsHelper: AnalyticsHelper, ) : Presenter { + @CircuitInject(LoginScreen::class, AppScope::class) + @AssistedFactory + fun interface Factory { + fun create(screen: LoginScreen, navigator: Navigator): LoginPresenter + } + companion object { private const val EVENT_ERROR_LOGIN = "error_login" } @@ -127,13 +134,4 @@ class LoginPresenter @AssistedInject constructor( eventSink = ::handleEvent, ) } - - @CircuitInject(LoginScreen::class, ActivityRetainedComponent::class) - @AssistedFactory - fun interface Factory { - fun create( - screen: LoginScreen, - navigator: Navigator, - ): LoginPresenter - } } diff --git a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginUi.kt b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginUi.kt index 4e6bcd93..2b0d8f83 100644 --- a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginUi.kt +++ b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginUi.kt @@ -35,10 +35,10 @@ import com.ninecraft.booket.core.ui.component.ReedLoadingIndicator import com.ninecraft.booket.feature.screens.LoginScreen import com.skydoves.compose.stability.runtime.TraceRecomposition import com.slack.circuit.codegen.annotations.CircuitInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope @TraceRecomposition -@CircuitInject(LoginScreen::class, ActivityRetainedComponent::class) +@CircuitInject(LoginScreen::class, AppScope::class) @Composable internal fun LoginUi( state: LoginUiState, diff --git a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/termsagreement/TermsAgreementPresenter.kt b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/termsagreement/TermsAgreementPresenter.kt index a1f4cf04..142d0d3f 100644 --- a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/termsagreement/TermsAgreementPresenter.kt +++ b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/termsagreement/TermsAgreementPresenter.kt @@ -20,21 +20,28 @@ import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.popUntil import com.slack.circuit.runtime.presenter.Presenter import com.slack.circuitx.effects.ImpressionEffect -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedFactory +import dev.zacsweers.metro.AssistedInject import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.launch -class TermsAgreementPresenter @AssistedInject constructor( +@AssistedInject +class TermsAgreementPresenter( @Assisted private val screen: TermsAgreementScreen, @Assisted private val navigator: Navigator, private val userRepository: UserRepository, private val analyticsHelper: AnalyticsHelper, ) : Presenter { + @CircuitInject(TermsAgreementScreen::class, AppScope::class) + @AssistedFactory + fun interface Factory { + fun create(screen: TermsAgreementScreen, navigator: Navigator): TermsAgreementPresenter + } + @Composable override fun present(): TermsAgreementUiState { val scope = rememberCoroutineScope() @@ -101,13 +108,4 @@ class TermsAgreementPresenter @AssistedInject constructor( eventSink = ::handleEvent, ) } - - @CircuitInject(TermsAgreementScreen::class, ActivityRetainedComponent::class) - @AssistedFactory - fun interface Factory { - fun create( - screen: TermsAgreementScreen, - navigator: Navigator, - ): TermsAgreementPresenter - } } diff --git a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/termsagreement/TermsAgreementUi.kt b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/termsagreement/TermsAgreementUi.kt index 2443ce0b..39fe1ada 100644 --- a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/termsagreement/TermsAgreementUi.kt +++ b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/termsagreement/TermsAgreementUi.kt @@ -31,11 +31,11 @@ import com.ninecraft.booket.feature.screens.TermsAgreementScreen import com.ninecraft.booket.feature.termsagreement.component.TermItem import com.skydoves.compose.stability.runtime.TraceRecomposition import com.slack.circuit.codegen.annotations.CircuitInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope import kotlinx.collections.immutable.persistentListOf @TraceRecomposition -@CircuitInject(TermsAgreementScreen::class, ActivityRetainedComponent::class) +@CircuitInject(TermsAgreementScreen::class, AppScope::class) @Composable internal fun TermsAgreementUi( state: TermsAgreementUiState, diff --git a/feature/login/stability/login.stability b/feature/login/stability/login.stability index 0d5e7076..43c486b5 100644 --- a/feature/login/stability/login.stability +++ b/feature/login/stability/login.stability @@ -9,7 +9,7 @@ internal fun com.ninecraft.booket.feature.login.HandleLoginSideEffects(state: co skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - eventSink: STABLE (function type) @Composable @@ -29,7 +29,7 @@ internal fun com.ninecraft.booket.feature.login.LoginUi(state: com.ninecraft.boo skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - modifier: STABLE (marked @Stable or @Immutable) @Composable @@ -37,7 +37,7 @@ internal fun com.ninecraft.booket.feature.termsagreement.HandleTermsAgreementSid skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) @Composable public fun com.ninecraft.booket.feature.termsagreement.TermsAgreementPresenter.present(): com.ninecraft.booket.feature.termsagreement.TermsAgreementUiState @@ -56,7 +56,7 @@ internal fun com.ninecraft.booket.feature.termsagreement.TermsAgreementUi(state: skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - modifier: STABLE (marked @Stable or @Immutable) @Composable diff --git a/feature/main/build.gradle.kts b/feature/main/build.gradle.kts index b5e1791e..05a1bb32 100644 --- a/feature/main/build.gradle.kts +++ b/feature/main/build.gradle.kts @@ -8,12 +8,10 @@ android { namespace = "com.ninecraft.booket.feature.main" } -ksp { - arg("circuit.codegen.mode", "hilt") -} - dependencies { implementations( + projects.core.di, + libs.androidx.activity.compose, libs.androidx.splash, diff --git a/feature/main/src/main/kotlin/com/ninecraft/booket/feature/main/MainActivity.kt b/feature/main/src/main/kotlin/com/ninecraft/booket/feature/main/MainActivity.kt index cdc440cb..6f793b50 100644 --- a/feature/main/src/main/kotlin/com/ninecraft/booket/feature/main/MainActivity.kt +++ b/feature/main/src/main/kotlin/com/ninecraft/booket/feature/main/MainActivity.kt @@ -1,5 +1,6 @@ package com.ninecraft.booket.feature.main +import android.app.Activity import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent @@ -17,20 +18,25 @@ import com.ninecraft.booket.core.common.event.EventHelper import com.ninecraft.booket.core.common.event.ReedEvent import com.ninecraft.booket.core.designsystem.theme.ReedTheme import com.ninecraft.booket.core.ui.component.ReedDialog +import com.ninecraft.booket.core.di.ActivityKey import com.ninecraft.booket.feature.screens.SplashScreen import com.slack.circuit.backstack.rememberSaveableBackStack import com.slack.circuit.foundation.Circuit import com.slack.circuit.foundation.CircuitCompositionLocals import com.slack.circuit.foundation.NavigableCircuitContent import com.slack.circuit.foundation.rememberCircuitNavigator -import dagger.hilt.android.AndroidEntryPoint +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesIntoMap +import dev.zacsweers.metro.Inject +import dev.zacsweers.metro.binding import tech.thdev.compose.exteions.system.ui.controller.rememberSystemUiController -import javax.inject.Inject -@AndroidEntryPoint -class MainActivity : ComponentActivity() { - @Inject - lateinit var circuit: Circuit +@ContributesIntoMap(AppScope::class, binding = binding()) +@ActivityKey(MainActivity::class) +@Inject +class MainActivity( + private val circuit: Circuit, +) : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { installSplashScreen() diff --git a/feature/onboarding/build.gradle.kts b/feature/onboarding/build.gradle.kts index 95b58994..d3c9277a 100644 --- a/feature/onboarding/build.gradle.kts +++ b/feature/onboarding/build.gradle.kts @@ -10,10 +10,6 @@ android { namespace = "com.ninecraft.booket.feature.onboarding" } -ksp { - arg("circuit.codegen.mode", "hilt") -} - dependencies { implementations( libs.logger, diff --git a/feature/onboarding/src/main/kotlin/com/ninecraft/booket/feature/onboarding/OnboardingPresenter.kt b/feature/onboarding/src/main/kotlin/com/ninecraft/booket/feature/onboarding/OnboardingPresenter.kt index 63304a27..76ed7295 100644 --- a/feature/onboarding/src/main/kotlin/com/ninecraft/booket/feature/onboarding/OnboardingPresenter.kt +++ b/feature/onboarding/src/main/kotlin/com/ninecraft/booket/feature/onboarding/OnboardingPresenter.kt @@ -12,20 +12,27 @@ import com.slack.circuit.codegen.annotations.CircuitInject import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.presenter.Presenter import com.slack.circuitx.effects.ImpressionEffect -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedFactory +import dev.zacsweers.metro.AssistedInject import kotlinx.coroutines.launch const val ONBOARDING_STEPS_COUNT = 3 -class OnboardingPresenter @AssistedInject constructor( +@AssistedInject +class OnboardingPresenter( @Assisted private val navigator: Navigator, private val repository: UserRepository, private val analyticsHelper: AnalyticsHelper, ) : Presenter { + @CircuitInject(OnboardingScreen::class, AppScope::class) + @AssistedFactory + fun interface Factory { + fun create(navigator: Navigator): OnboardingPresenter + } + @Composable override fun present(): OnboardingUiState { val scope = rememberCoroutineScope() @@ -59,10 +66,4 @@ class OnboardingPresenter @AssistedInject constructor( eventSink = ::handleEvent, ) } - - @CircuitInject(OnboardingScreen::class, ActivityRetainedComponent::class) - @AssistedFactory - fun interface Factory { - fun create(navigator: Navigator): OnboardingPresenter - } } diff --git a/feature/onboarding/src/main/kotlin/com/ninecraft/booket/feature/onboarding/OnboardingUi.kt b/feature/onboarding/src/main/kotlin/com/ninecraft/booket/feature/onboarding/OnboardingUi.kt index 444d9c4b..525d8a51 100644 --- a/feature/onboarding/src/main/kotlin/com/ninecraft/booket/feature/onboarding/OnboardingUi.kt +++ b/feature/onboarding/src/main/kotlin/com/ninecraft/booket/feature/onboarding/OnboardingUi.kt @@ -25,10 +25,10 @@ import com.ninecraft.booket.feature.onboarding.component.PagerIndicator import com.ninecraft.booket.feature.screens.OnboardingScreen import com.skydoves.compose.stability.runtime.TraceRecomposition import com.slack.circuit.codegen.annotations.CircuitInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope @TraceRecomposition -@CircuitInject(OnboardingScreen::class, ActivityRetainedComponent::class) +@CircuitInject(OnboardingScreen::class, AppScope::class) @Composable internal fun OnboardingUi( state: OnboardingUiState, diff --git a/feature/onboarding/stability/onboarding.stability b/feature/onboarding/stability/onboarding.stability index f01b11fc..4a020e64 100644 --- a/feature/onboarding/stability/onboarding.stability +++ b/feature/onboarding/stability/onboarding.stability @@ -21,7 +21,7 @@ internal fun com.ninecraft.booket.feature.onboarding.OnboardingUi(state: com.nin skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - modifier: STABLE (marked @Stable or @Immutable) @Composable diff --git a/feature/record/build.gradle.kts b/feature/record/build.gradle.kts index 8b49d90b..b3056c2c 100644 --- a/feature/record/build.gradle.kts +++ b/feature/record/build.gradle.kts @@ -10,10 +10,6 @@ android { namespace = "com.ninecraft.booket.feature.record" } -ksp { - arg("circuit.codegen.mode", "hilt") -} - dependencies { implementations( projects.core.ocr, diff --git a/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrPresenter.kt b/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrPresenter.kt index 84389369..1d050281 100644 --- a/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrPresenter.kt +++ b/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrPresenter.kt @@ -16,22 +16,29 @@ import com.slack.circuit.retained.rememberRetained import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.presenter.Presenter import com.slack.circuitx.effects.ImpressionEffect -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedFactory +import dev.zacsweers.metro.AssistedInject import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentSetOf import kotlinx.collections.immutable.toPersistentList import kotlinx.collections.immutable.toPersistentSet import kotlinx.coroutines.launch -class OcrPresenter @AssistedInject constructor( +@AssistedInject +class OcrPresenter( @Assisted private val navigator: Navigator, private val recognizer: CloudOcrRecognizer, private val analyticsHelper: AnalyticsHelper, ) : Presenter { + @CircuitInject(OcrScreen::class, AppScope::class) + @AssistedFactory + fun interface Factory { + fun create(navigator: Navigator): OcrPresenter + } + companion object { private const val RECORD_OCR_SENTENCE = "record_OCR_sentence" } @@ -167,10 +174,4 @@ class OcrPresenter @AssistedInject constructor( eventSink = ::handleEvent, ) } - - @CircuitInject(OcrScreen::class, ActivityRetainedComponent::class) - @AssistedFactory - fun interface Factory { - fun create(navigator: Navigator): OcrPresenter - } } diff --git a/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrUi.kt b/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrUi.kt index 82b9c56c..9892be46 100644 --- a/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrUi.kt +++ b/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrUi.kt @@ -70,13 +70,13 @@ import com.ninecraft.booket.feature.record.ocr.component.SentenceBox import com.ninecraft.booket.feature.screens.OcrScreen import com.skydoves.compose.stability.runtime.TraceRecomposition import com.slack.circuit.codegen.annotations.CircuitInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope import tech.thdev.compose.exteions.system.ui.controller.rememberSystemUiController import java.io.File import com.ninecraft.booket.core.designsystem.R as designR @TraceRecomposition -@CircuitInject(OcrScreen::class, ActivityRetainedComponent::class) +@CircuitInject(OcrScreen::class, AppScope::class) @Composable internal fun OcrUi( state: OcrUiState, diff --git a/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt b/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt index 16abd5ca..62581d60 100644 --- a/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt +++ b/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt @@ -27,20 +27,27 @@ import com.slack.circuit.retained.rememberRetained import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.presenter.Presenter import com.slack.circuitx.effects.ImpressionEffect -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedFactory +import dev.zacsweers.metro.AssistedInject import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.launch -class RecordRegisterPresenter @AssistedInject constructor( +@AssistedInject +class RecordRegisterPresenter( @Assisted private val screen: RecordScreen, @Assisted private val navigator: Navigator, private val repository: RecordRepository, private val analyticsHelper: AnalyticsHelper, ) : Presenter { + @CircuitInject(RecordScreen::class, AppScope::class) + @AssistedFactory + fun interface Factory { + fun create(screen: RecordScreen, navigator: Navigator): RecordRegisterPresenter + } + companion object { private const val MAX_PAGE = 4032 private const val RECORD_INPUT_SENTENCE = "record_input_sentence" @@ -309,13 +316,4 @@ class RecordRegisterPresenter @AssistedInject constructor( eventSink = ::handleEvent, ) } - - @CircuitInject(RecordScreen::class, ActivityRetainedComponent::class) - @AssistedFactory - fun interface Factory { - fun create( - screen: RecordScreen, - navigator: Navigator, - ): RecordRegisterPresenter - } } diff --git a/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUi.kt b/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUi.kt index 255338ce..50e67d41 100644 --- a/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUi.kt +++ b/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUi.kt @@ -33,10 +33,10 @@ import com.ninecraft.booket.feature.record.step.QuoteStep import com.ninecraft.booket.feature.screens.RecordScreen import com.skydoves.compose.stability.runtime.TraceRecomposition import com.slack.circuit.codegen.annotations.CircuitInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope @TraceRecomposition -@CircuitInject(RecordScreen::class, ActivityRetainedComponent::class) +@CircuitInject(RecordScreen::class, AppScope::class) @Composable internal fun RecordRegisterUi( state: RecordRegisterUiState, diff --git a/feature/record/stability/record.stability b/feature/record/stability/record.stability index 21fb836f..0198cadd 100644 --- a/feature/record/stability/record.stability +++ b/feature/record/stability/record.stability @@ -59,7 +59,7 @@ private fun com.ninecraft.booket.feature.record.ocr.CameraPreview(state: com.nin skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - modifier: STABLE (marked @Stable or @Immutable) @Composable @@ -73,7 +73,7 @@ internal fun com.ninecraft.booket.feature.record.ocr.HandleOcrSideEffects(state: skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) @Composable public fun com.ninecraft.booket.feature.record.ocr.OcrPresenter.present(): com.ninecraft.booket.feature.record.ocr.OcrUiState @@ -86,7 +86,7 @@ internal fun com.ninecraft.booket.feature.record.ocr.OcrUi(state: com.ninecraft. skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - modifier: STABLE (marked @Stable or @Immutable) @Composable @@ -100,7 +100,7 @@ private fun com.ninecraft.booket.feature.record.ocr.TextScanResult(state: com.ni skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - modifier: STABLE (marked @Stable or @Immutable) @Composable @@ -137,7 +137,7 @@ internal fun com.ninecraft.booket.feature.record.register.HandleRecordRegisterSi skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) @Composable public fun com.ninecraft.booket.feature.record.register.RecordRegisterPresenter.present(): com.ninecraft.booket.feature.record.register.RecordRegisterUiState @@ -156,15 +156,15 @@ internal fun com.ninecraft.booket.feature.record.register.RecordRegisterUi(state skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - modifier: STABLE (marked @Stable or @Immutable) @Composable -private fun com.ninecraft.booket.feature.record.step.EmotionItem(emotionTag: com.ninecraft.booket.core.designsystem.EmotionTag, onClick: kotlin.Function0, isSelected: kotlin.Boolean, modifier: androidx.compose.ui.Modifier): kotlin.Unit +private fun com.ninecraft.booket.feature.record.step.EmotionItem(emotion: com.ninecraft.booket.core.model.Emotion, onClick: kotlin.Function0, isSelected: kotlin.Boolean, modifier: androidx.compose.ui.Modifier): kotlin.Unit skippable: true restartable: true params: - - emotionTag: STABLE + - emotion: STABLE (class with no mutable properties) - onClick: STABLE (function type) - isSelected: STABLE (primitive type) - modifier: STABLE (marked @Stable or @Immutable) @@ -174,7 +174,7 @@ public fun com.ninecraft.booket.feature.record.step.EmotionStep(state: com.ninec skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - modifier: STABLE (marked @Stable or @Immutable) @Composable @@ -182,7 +182,7 @@ public fun com.ninecraft.booket.feature.record.step.ImpressionStep(state: com.ni skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - modifier: STABLE (marked @Stable or @Immutable) @Composable @@ -196,7 +196,7 @@ internal fun com.ninecraft.booket.feature.record.step.QuoteStep(state: com.ninec skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - modifier: STABLE (marked @Stable or @Immutable) @Composable diff --git a/feature/screens/stability/screens.stability b/feature/screens/stability/screens.stability index aaa1f6b6..a47d75b1 100644 --- a/feature/screens/stability/screens.stability +++ b/feature/screens/stability/screens.stability @@ -10,7 +10,7 @@ public fun com.ninecraft.booket.feature.screens.component.MainBottomBar(tabs: ko restartable: true params: - tabs: STABLE (known stable type) - - currentTab: STABLE + - currentTab: STABLE (class with no mutable properties) - onTabSelected: STABLE (function type) - modifier: STABLE (marked @Stable or @Immutable) @@ -19,7 +19,7 @@ private fun com.ninecraft.booket.feature.screens.component.MainBottomBarItem(tab skippable: true restartable: true params: - - tab: STABLE + - tab: STABLE (class with no mutable properties) - selected: STABLE (primitive type) - onClick: STABLE (function type) diff --git a/feature/search/build.gradle.kts b/feature/search/build.gradle.kts index ff1f0354..0a6fb604 100644 --- a/feature/search/build.gradle.kts +++ b/feature/search/build.gradle.kts @@ -10,10 +10,6 @@ android { namespace = "com.ninecraft.booket.feature.search" } -ksp { - arg("circuit.codegen.mode", "hilt") -} - dependencies { implementations( libs.kotlinx.collections.immutable, diff --git a/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchPresenter.kt b/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchPresenter.kt index e78e03f7..8699c1e8 100644 --- a/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchPresenter.kt +++ b/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchPresenter.kt @@ -31,21 +31,29 @@ import com.slack.circuit.retained.rememberRetained import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.presenter.Presenter import com.slack.circuitx.effects.ImpressionEffect -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedFactory +import dev.zacsweers.metro.AssistedInject import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.launch -class BookSearchPresenter @AssistedInject constructor( +@AssistedInject +class BookSearchPresenter( @Assisted private val navigator: Navigator, private val repository: BookRepository, private val authRepository: AuthRepository, private val analyticsHelper: AnalyticsHelper, ) : Presenter { + + @CircuitInject(BookSearchScreen::class, AppScope::class) + @AssistedFactory + fun interface Factory { + fun create(navigator: Navigator): BookSearchPresenter + } + companion object { private const val START_INDEX = 1 private const val SEARCH_BOOK_RESULT = "search_book_result" @@ -276,10 +284,4 @@ class BookSearchPresenter @AssistedInject constructor( eventSink = ::handleEvent, ) } - - @CircuitInject(BookSearchScreen::class, ActivityRetainedComponent::class) - @AssistedFactory - fun interface Factory { - fun create(navigator: Navigator): BookSearchPresenter - } } diff --git a/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchUi.kt b/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchUi.kt index 5904f258..6f55c5b6 100644 --- a/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchUi.kt +++ b/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchUi.kt @@ -42,13 +42,13 @@ import com.ninecraft.booket.feature.search.common.component.RecentSearchTitle import com.ninecraft.booket.feature.search.common.component.SearchItem import com.skydoves.compose.stability.runtime.TraceRecomposition import com.slack.circuit.codegen.annotations.CircuitInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.launch import com.ninecraft.booket.core.designsystem.R as designR @TraceRecomposition -@CircuitInject(BookSearchScreen::class, ActivityRetainedComponent::class) +@CircuitInject(BookSearchScreen::class, AppScope::class) @Composable internal fun BookSearchUi( state: BookSearchUiState, diff --git a/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/library/LibrarySearchPresenter.kt b/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/library/LibrarySearchPresenter.kt index fc4799d0..b0ffd869 100644 --- a/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/library/LibrarySearchPresenter.kt +++ b/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/library/LibrarySearchPresenter.kt @@ -23,20 +23,28 @@ import com.slack.circuit.retained.rememberRetained import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.presenter.Presenter import com.slack.circuitx.effects.ImpressionEffect -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedFactory +import dev.zacsweers.metro.AssistedInject import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.launch -class LibrarySearchPresenter @AssistedInject constructor( +@AssistedInject +class LibrarySearchPresenter( @Assisted private val navigator: Navigator, private val repository: BookRepository, private val analyticsHelper: AnalyticsHelper, ) : Presenter { + + @CircuitInject(LibrarySearchScreen::class, AppScope::class) + @AssistedFactory + fun interface Factory { + fun create(navigator: Navigator): LibrarySearchPresenter + } + companion object { private const val PAGE_SIZE = 20 private const val START_INDEX = 0 @@ -172,10 +180,4 @@ class LibrarySearchPresenter @AssistedInject constructor( eventSink = ::handleEvent, ) } - - @CircuitInject(LibrarySearchScreen::class, ActivityRetainedComponent::class) - @AssistedFactory - fun interface Factory { - fun create(navigator: Navigator): LibrarySearchPresenter - } } diff --git a/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/library/LibrarySearchUi.kt b/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/library/LibrarySearchUi.kt index 227d3ad0..c535add3 100644 --- a/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/library/LibrarySearchUi.kt +++ b/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/library/LibrarySearchUi.kt @@ -35,10 +35,10 @@ import com.ninecraft.booket.feature.search.common.component.SearchItem import com.ninecraft.booket.feature.search.library.component.LibraryBookItem import com.skydoves.compose.stability.runtime.TraceRecomposition import com.slack.circuit.codegen.annotations.CircuitInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope @TraceRecomposition -@CircuitInject(LibrarySearchScreen::class, ActivityRetainedComponent::class) +@CircuitInject(LibrarySearchScreen::class, AppScope::class) @Composable internal fun LibrarySearchUi( state: LibrarySearchUiState, diff --git a/feature/search/stability/search.stability b/feature/search/stability/search.stability index b2786505..6756f3dc 100644 --- a/feature/search/stability/search.stability +++ b/feature/search/stability/search.stability @@ -9,7 +9,7 @@ internal fun com.ninecraft.booket.feature.search.book.BookSearchContent(state: c skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - modifier: STABLE (marked @Stable or @Immutable) @Composable @@ -29,7 +29,7 @@ internal fun com.ninecraft.booket.feature.search.book.BookSearchUi(state: com.ni skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - modifier: STABLE (marked @Stable or @Immutable) @Composable @@ -37,7 +37,7 @@ internal fun com.ninecraft.booket.feature.search.book.HandleBookSearchSideEffect skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - eventSink: STABLE (function type) @Composable @@ -65,7 +65,7 @@ public fun com.ninecraft.booket.feature.search.book.component.BookRegisterBottom - sheetState: STABLE (marked @Stable or @Immutable) - onCloseButtonClick: STABLE (function type) - bookStatuses: STABLE (known stable type) - - currentBookStatus: STABLE + - currentBookStatus: STABLE (class with no mutable properties) - onItemSelected: STABLE (function type) - onBookRegisterButtonClick: STABLE (function type) - modifier: STABLE (marked @Stable or @Immutable) @@ -89,7 +89,7 @@ public fun com.ninecraft.booket.feature.search.book.component.BookRegisterSucces params: - onDismissRequest: STABLE (function type) - sheetState: STABLE (marked @Stable or @Immutable) - - upsertedBookStatus: STABLE + - upsertedBookStatus: STABLE (class with no mutable properties) - onCancelButtonClick: STABLE (function type) - onOKButtonClick: STABLE (function type) - modifier: STABLE (marked @Stable or @Immutable) @@ -111,7 +111,7 @@ public fun com.ninecraft.booket.feature.search.book.component.BookStatusItem(ite skippable: true restartable: true params: - - item: STABLE + - item: STABLE (class with no mutable properties) - selected: STABLE (primitive type) - onClick: STABLE (function type) - modifier: STABLE (marked @Stable or @Immutable) @@ -150,14 +150,14 @@ internal fun com.ninecraft.booket.feature.search.library.HandlingLibrarySearchSi skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) @Composable internal fun com.ninecraft.booket.feature.search.library.LibrarySearchContent(state: com.ninecraft.booket.feature.search.library.LibrarySearchUiState, innerPadding: androidx.compose.foundation.layout.PaddingValues, modifier: androidx.compose.ui.Modifier): kotlin.Unit skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - innerPadding: STABLE (marked @Stable or @Immutable) - modifier: STABLE (marked @Stable or @Immutable) @@ -178,7 +178,7 @@ internal fun com.ninecraft.booket.feature.search.library.LibrarySearchUi(state: skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - modifier: STABLE (marked @Stable or @Immutable) @Composable diff --git a/feature/settings/build.gradle.kts b/feature/settings/build.gradle.kts index 9ecba147..eedc0280 100644 --- a/feature/settings/build.gradle.kts +++ b/feature/settings/build.gradle.kts @@ -10,10 +10,6 @@ android { namespace = "com.ninecraft.booket.feature.settings" } -ksp { - arg("circuit.codegen.mode", "hilt") -} - dependencies { implementations( libs.logger, diff --git a/feature/settings/src/main/assets/oss_licenses.json b/feature/settings/src/main/assets/oss_licenses.json index ce75a00b..1a1b6e7e 100644 --- a/feature/settings/src/main/assets/oss_licenses.json +++ b/feature/settings/src/main/assets/oss_licenses.json @@ -15,9 +15,9 @@ "url": "https://github.com/skydoves/compose-stable-marker" }, { - "name": "Hilt", + "name": "Metro", "license": "Apache License 2.0", - "url": "https://dagger.dev/hilt/" + "url": "https://zacsweers.github.io/metro" }, { "name": "Logger", diff --git a/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsPresenter.kt b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsPresenter.kt index 003a09bc..613c2d6a 100644 --- a/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsPresenter.kt +++ b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsPresenter.kt @@ -26,13 +26,14 @@ import com.slack.circuit.retained.rememberRetained import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.presenter.Presenter import com.slack.circuitx.effects.ImpressionEffect -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedFactory +import dev.zacsweers.metro.AssistedInject import kotlinx.coroutines.launch -class SettingsPresenter @AssistedInject constructor( +@AssistedInject +class SettingsPresenter( @Assisted val navigator: Navigator, private val authRepository: AuthRepository, private val userRepository: UserRepository, @@ -40,6 +41,12 @@ class SettingsPresenter @AssistedInject constructor( private val analyticsHelper: AnalyticsHelper, ) : Presenter { + @CircuitInject(SettingsScreen::class, AppScope::class) + @AssistedFactory + fun interface Factory { + fun create(navigator: Navigator): SettingsPresenter + } + companion object { private const val SETTINGS_LOGOUT_COMPLETE = "settings_logout_complete" private const val SETTINGS_WITHDRAWAL_COMPLETE = "settings_withdrawal_complete" @@ -232,10 +239,4 @@ class SettingsPresenter @AssistedInject constructor( eventSink = ::handleEvent, ) } - - @CircuitInject(SettingsScreen::class, ActivityRetainedComponent::class) - @AssistedFactory - fun interface Factory { - fun create(navigator: Navigator): SettingsPresenter - } } diff --git a/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsUi.kt b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsUi.kt index 8977e64b..29508122 100644 --- a/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsUi.kt +++ b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsUi.kt @@ -37,13 +37,13 @@ import com.ninecraft.booket.feature.settings.component.SettingItem import com.ninecraft.booket.feature.settings.component.WithdrawConfirmationBottomSheet import com.skydoves.compose.stability.runtime.TraceRecomposition import com.slack.circuit.codegen.annotations.CircuitInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope import kotlinx.coroutines.launch import com.ninecraft.booket.core.designsystem.R as designR @TraceRecomposition @OptIn(ExperimentalMaterial3Api::class) -@CircuitInject(SettingsScreen::class, ActivityRetainedComponent::class) +@CircuitInject(SettingsScreen::class, AppScope::class) @Composable internal fun SettingsUi( state: SettingsUiState, diff --git a/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/notification/NotificationPresenter.kt b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/notification/NotificationPresenter.kt index 7152080b..4d6e789a 100644 --- a/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/notification/NotificationPresenter.kt +++ b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/notification/NotificationPresenter.kt @@ -16,16 +16,24 @@ import com.slack.circuit.retained.collectAsRetainedState import com.slack.circuit.retained.rememberRetained import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.presenter.Presenter -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedFactory +import dev.zacsweers.metro.AssistedInject import kotlinx.coroutines.launch -class NotificationPresenter @AssistedInject constructor( +@AssistedInject +class NotificationPresenter( @Assisted val navigator: Navigator, private val userRepository: UserRepository, ) : Presenter { + + @CircuitInject(NotificationScreen::class, AppScope::class) + @AssistedFactory + fun interface Factory { + fun create(navigator: Navigator): NotificationPresenter + } + @Composable override fun present(): NotificationUiState { val scope = rememberCoroutineScope() @@ -117,10 +125,4 @@ class NotificationPresenter @AssistedInject constructor( eventSink = ::handleEvent, ) } - - @CircuitInject(NotificationScreen::class, ActivityRetainedComponent::class) - @AssistedFactory - fun interface Factory { - fun create(navigator: Navigator): NotificationPresenter - } } diff --git a/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/notification/NotificationUi.kt b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/notification/NotificationUi.kt index 66a86385..f4b23a79 100644 --- a/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/notification/NotificationUi.kt +++ b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/notification/NotificationUi.kt @@ -45,11 +45,11 @@ import com.ninecraft.booket.feature.settings.R import com.ninecraft.booket.feature.settings.component.ToggleItem import com.skydoves.compose.stability.runtime.TraceRecomposition import com.slack.circuit.codegen.annotations.CircuitInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope import com.ninecraft.booket.core.designsystem.R as designR @TraceRecomposition -@CircuitInject(NotificationScreen::class, ActivityRetainedComponent::class) +@CircuitInject(NotificationScreen::class, AppScope::class) @Composable internal fun NotificationUi( state: NotificationUiState, diff --git a/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/osslicenses/OssLicensesPresenter.kt b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/osslicenses/OssLicensesPresenter.kt index e9f23d27..200db811 100644 --- a/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/osslicenses/OssLicensesPresenter.kt +++ b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/osslicenses/OssLicensesPresenter.kt @@ -5,14 +5,22 @@ import com.ninecraft.booket.feature.screens.OssLicensesScreen import com.slack.circuit.codegen.annotations.CircuitInject import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.presenter.Presenter -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedFactory +import dev.zacsweers.metro.AssistedInject -class OssLicensesPresenter @AssistedInject constructor( +@AssistedInject +class OssLicensesPresenter( @Assisted val navigator: Navigator, ) : Presenter { + + @CircuitInject(OssLicensesScreen::class, AppScope::class) + @AssistedFactory + fun interface Factory { + fun create(navigator: Navigator): OssLicensesPresenter + } + @Composable override fun present(): OssLicensesUiState { fun handleEvent(event: OssLicensesUiEvent) { @@ -26,10 +34,4 @@ class OssLicensesPresenter @AssistedInject constructor( eventSink = ::handleEvent, ) } - - @CircuitInject(OssLicensesScreen::class, ActivityRetainedComponent::class) - @AssistedFactory - fun interface Factory { - fun create(navigator: Navigator): OssLicensesPresenter - } } diff --git a/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/osslicenses/OssLicensesUi.kt b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/osslicenses/OssLicensesUi.kt index e6380a55..b08b8a5e 100644 --- a/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/osslicenses/OssLicensesUi.kt +++ b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/osslicenses/OssLicensesUi.kt @@ -39,14 +39,14 @@ import com.ninecraft.booket.feature.screens.OssLicensesScreen import com.orhanobut.logger.Logger import com.skydoves.compose.stability.runtime.TraceRecomposition import com.slack.circuit.codegen.annotations.CircuitInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlinx.serialization.json.Json import java.io.IOException @TraceRecomposition -@CircuitInject(OssLicensesScreen::class, ActivityRetainedComponent::class) +@CircuitInject(OssLicensesScreen::class, AppScope::class) @Composable internal fun OssLicenses( state: OssLicensesUiState, diff --git a/feature/settings/stability/settings.stability b/feature/settings/stability/settings.stability index bb954ee0..1d72f777 100644 --- a/feature/settings/stability/settings.stability +++ b/feature/settings/stability/settings.stability @@ -9,7 +9,7 @@ internal fun com.ninecraft.booket.feature.settings.HandleSettingsSideEffects(sta skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - eventSink: STABLE (function type) @Composable @@ -29,7 +29,7 @@ internal fun com.ninecraft.booket.feature.settings.SettingsUi(state: com.ninecra skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - modifier: STABLE (marked @Stable or @Immutable) @Composable @@ -105,7 +105,7 @@ internal fun com.ninecraft.booket.feature.settings.notification.HandleNotificati skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - eventSink: STABLE (function type) @Composable @@ -127,7 +127,7 @@ internal fun com.ninecraft.booket.feature.settings.notification.NotificationUi(s skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - modifier: STABLE (marked @Stable or @Immutable) @Composable @@ -151,7 +151,7 @@ internal fun com.ninecraft.booket.feature.settings.osslicenses.OssLicenses(state skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - modifier: STABLE (marked @Stable or @Immutable) @Composable diff --git a/feature/splash/build.gradle.kts b/feature/splash/build.gradle.kts index a758e787..832cbd8a 100644 --- a/feature/splash/build.gradle.kts +++ b/feature/splash/build.gradle.kts @@ -8,10 +8,6 @@ android { namespace = "com.ninecraft.booket.feature.splash" } -ksp { - arg("circuit.codegen.mode", "hilt") -} - dependencies { implementations( libs.compose.system.ui.controller, diff --git a/feature/splash/src/main/kotlin/com/ninecraft/booket/splash/SplashPresenter.kt b/feature/splash/src/main/kotlin/com/ninecraft/booket/splash/SplashPresenter.kt index 02a0761e..398affca 100644 --- a/feature/splash/src/main/kotlin/com/ninecraft/booket/splash/SplashPresenter.kt +++ b/feature/splash/src/main/kotlin/com/ninecraft/booket/splash/SplashPresenter.kt @@ -25,14 +25,15 @@ import com.slack.circuit.retained.rememberRetained import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.presenter.Presenter import com.slack.circuitx.effects.ImpressionEffect -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedFactory +import dev.zacsweers.metro.AssistedInject import kotlinx.coroutines.delay import kotlinx.coroutines.launch -class SplashPresenter @AssistedInject constructor( +@AssistedInject +class SplashPresenter( @Assisted private val navigator: Navigator, private val userRepository: UserRepository, private val authRepository: AuthRepository, @@ -40,6 +41,12 @@ class SplashPresenter @AssistedInject constructor( private val analyticsHelper: AnalyticsHelper, ) : Presenter { + @CircuitInject(SplashScreen::class, AppScope::class) + @AssistedFactory + fun interface Factory { + fun create(navigator: Navigator): SplashPresenter + } + @Composable override fun present(): SplashUiState { val scope = rememberCoroutineScope() @@ -147,10 +154,4 @@ class SplashPresenter @AssistedInject constructor( eventSink = ::handleEvent, ) } - - @CircuitInject(SplashScreen::class, ActivityRetainedComponent::class) - @AssistedFactory - fun interface Factory { - fun create(navigator: Navigator): SplashPresenter - } } diff --git a/feature/splash/src/main/kotlin/com/ninecraft/booket/splash/SplashUi.kt b/feature/splash/src/main/kotlin/com/ninecraft/booket/splash/SplashUi.kt index d60631d0..b68483bb 100644 --- a/feature/splash/src/main/kotlin/com/ninecraft/booket/splash/SplashUi.kt +++ b/feature/splash/src/main/kotlin/com/ninecraft/booket/splash/SplashUi.kt @@ -26,11 +26,11 @@ import com.ninecraft.booket.feature.screens.SplashScreen import com.ninecraft.booket.feature.splash.R import com.skydoves.compose.stability.runtime.TraceRecomposition import com.slack.circuit.codegen.annotations.CircuitInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope import tech.thdev.compose.exteions.system.ui.controller.rememberSystemUiController @TraceRecomposition -@CircuitInject(SplashScreen::class, ActivityRetainedComponent::class) +@CircuitInject(SplashScreen::class, AppScope::class) @Composable fun SplashUi( state: SplashUiState, diff --git a/feature/splash/stability/splash.stability b/feature/splash/stability/splash.stability index f00ec242..bc5f1e0e 100644 --- a/feature/splash/stability/splash.stability +++ b/feature/splash/stability/splash.stability @@ -9,7 +9,7 @@ internal fun com.ninecraft.booket.splash.HandleSplashSideEffects(state: com.nine skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - eventSink: STABLE (function type) @Composable @@ -29,6 +29,6 @@ public fun com.ninecraft.booket.splash.SplashUi(state: com.ninecraft.booket.spla skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - modifier: STABLE (marked @Stable or @Immutable) diff --git a/feature/webview/build.gradle.kts b/feature/webview/build.gradle.kts index 46adfe38..377d1c56 100644 --- a/feature/webview/build.gradle.kts +++ b/feature/webview/build.gradle.kts @@ -10,10 +10,6 @@ android { namespace = "com.ninecraft.booket.feature.webview" } -ksp { - arg("circuit.codegen.mode", "hilt") -} - dependencies { implementations( libs.logger, diff --git a/feature/webview/src/main/kotlin/com/ninecraft/booket/feature/webview/WebViewPresenter.kt b/feature/webview/src/main/kotlin/com/ninecraft/booket/feature/webview/WebViewPresenter.kt index 1483a0c6..662d6857 100644 --- a/feature/webview/src/main/kotlin/com/ninecraft/booket/feature/webview/WebViewPresenter.kt +++ b/feature/webview/src/main/kotlin/com/ninecraft/booket/feature/webview/WebViewPresenter.kt @@ -5,16 +5,23 @@ import com.ninecraft.booket.feature.screens.WebViewScreen import com.slack.circuit.codegen.annotations.CircuitInject import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.presenter.Presenter -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedFactory +import dev.zacsweers.metro.AssistedInject -class WebViewPresenter @AssistedInject constructor( +@AssistedInject +class WebViewPresenter( @Assisted private val screen: WebViewScreen, @Assisted private val navigator: Navigator, ) : Presenter { + @CircuitInject(WebViewScreen::class, AppScope::class) + @AssistedFactory + fun interface Factory { + fun create(screen: WebViewScreen, navigator: Navigator): WebViewPresenter + } + @Composable override fun present(): WebViewUiState { fun handleEvent(event: WebViewUiEvent) { @@ -31,13 +38,4 @@ class WebViewPresenter @AssistedInject constructor( eventSink = ::handleEvent, ) } - - @CircuitInject(WebViewScreen::class, ActivityRetainedComponent::class) - @AssistedFactory - fun interface Factory { - fun create( - screen: WebViewScreen, - navigator: Navigator, - ): WebViewPresenter - } } diff --git a/feature/webview/src/main/kotlin/com/ninecraft/booket/feature/webview/WebViewUi.kt b/feature/webview/src/main/kotlin/com/ninecraft/booket/feature/webview/WebViewUi.kt index 4835072d..279ab2ca 100644 --- a/feature/webview/src/main/kotlin/com/ninecraft/booket/feature/webview/WebViewUi.kt +++ b/feature/webview/src/main/kotlin/com/ninecraft/booket/feature/webview/WebViewUi.kt @@ -17,9 +17,9 @@ import com.ninecraft.booket.core.ui.ReedScaffold import com.ninecraft.booket.core.ui.component.ReedBackTopAppBar import com.ninecraft.booket.feature.screens.WebViewScreen import com.slack.circuit.codegen.annotations.CircuitInject -import dagger.hilt.android.components.ActivityRetainedComponent +import dev.zacsweers.metro.AppScope -@CircuitInject(WebViewScreen::class, ActivityRetainedComponent::class) +@CircuitInject(WebViewScreen::class, AppScope::class) @Composable internal fun WebViewUi( state: WebViewUiState, diff --git a/feature/webview/stability/webview.stability b/feature/webview/stability/webview.stability index 936e51ef..20982550 100644 --- a/feature/webview/stability/webview.stability +++ b/feature/webview/stability/webview.stability @@ -9,7 +9,7 @@ internal fun com.ninecraft.booket.feature.webview.WebViewContent(state: com.nine skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - innerPadding: STABLE (marked @Stable or @Immutable) - modifier: STABLE (marked @Stable or @Immutable) @@ -24,7 +24,7 @@ internal fun com.ninecraft.booket.feature.webview.WebViewUi(state: com.ninecraft skippable: true restartable: true params: - - state: STABLE + - state: STABLE (class with no mutable properties) - modifier: STABLE (marked @Stable or @Immutable) @Composable diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 53eb5898..b3f5690b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,17 +14,17 @@ android-gradle-plugin = "8.9.3" androidx-core = "1.16.0" androidx-activity-compose = "1.10.1" androidx-startup = "1.2.0" -androidx-splash = "1.0.1" +androidx-splash = "1.2.0" androidx-datastore = "1.1.7" -androidx-camera = "1.4.2" +androidx-camera = "1.5.1" ## Compose androidx-compose-bom = "2025.07.00" -androidx-compose-material3 = "1.4.0-alpha18" -compose-stable-marker = "1.0.6" -compose-effects = "0.1.1" +androidx-compose-material3 = "1.4.0" +compose-stable-marker = "1.0.7" +compose-effects = "0.1.4" compose-shadow = "2.0.4" -compose-stability-analyzer = "0.4.2" +compose-stability-analyzer = "0.5.2" ## Kotlin Symbol Processing ksp = "2.3.0" @@ -35,9 +35,8 @@ kotlinx-coroutines = "1.10.2" kotlinx-serialization-json = "1.9.0" kotlinx-collections-immutable = "0.4.0" -## Hilt -hilt = "2.57" -hilt-navigation-compose = "1.2.0" +## Metro +metro = "0.7.7" ## Network okhttp = "5.1.0" @@ -50,14 +49,14 @@ circuit = "0.30.0" logger = "2.2.0" ## Kakao Login -kakao-core = "2.21.4" +kakao-core = "2.22.0" ## Image Load coil-compose = "2.7.0" landscapist = "2.5.1" ## Lottie -lottie = "6.6.6" +lottie = "6.7.1" ## Extension # https://github.com/jisungbin/dependency-handler-extensions @@ -83,6 +82,7 @@ firebase-crashlytics = "3.0.4" android-gradle-plugin = { group = "com.android.tools.build", name = "gradle", version.ref = "android-gradle-plugin" } kotlin-gradle-plugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } compose-compiler-gradle-plugin = { group = "org.jetbrains.kotlin", name = "compose-compiler-gradle-plugin", version.ref = "kotlin" } +ksp-gradle-plugin = { group = "com.google.devtools.ksp", name = "com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidx-core" } androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "androidx-activity-compose" } @@ -110,10 +110,6 @@ compose-system-ui-controller = { group = "tech.thdev", name = "extensions-compos compose-keyboard-state = { group = "tech.thdev", name = "extensions-compose-keyboard-state", version.ref = "compose-extensions" } compose-shadow = { group = "com.adamglin", name = "compose-shadow", version.ref = "compose-shadow" } -hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } -hilt-android-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" } -hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hilt-navigation-compose" } - retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" } retrofit-kotlinx-serialization-converter = { module = "com.squareup.retrofit2:converter-kotlinx-serialization", version.ref = "retrofit" } okhttp-logging-interceptor = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" } @@ -168,13 +164,13 @@ kotlin-detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "kotlin-dete kotlin-ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "kotlin-ktlint-gradle" } kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" } +metro = { id = "dev.zacsweers.metro", version.ref = "metro" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } -hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } google-service = { id = "com.google.gms.google-services", version.ref = "google-service" } firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebase-crashlytics" } -compose-stability-analyzer = { id = "com.github.skydoves.compose.stability.analyzer", version.ref = "compose-stability-analyzer"} +compose-stability-analyzer = { id = "com.github.skydoves.compose.stability.analyzer", version.ref = "compose-stability-analyzer" } # Plugins defined by this project booket-android-application = { id = "booket.android.application", version = "unspecified" } @@ -184,7 +180,6 @@ booket-android-library-compose = { id = "booket.android.library.compose", versio booket-android-retrofit = { id = "booket.android.retrofit", version = "unspecified" } booket-android-feature = { id = "booket.android.feature", version = "unspecified" } booket-android-firebase = { id = "booket.android.firebase", version = "unspecified" } -booket-android-hilt = { id = "booket.android.hilt", version = "unspecified" } booket-jvm-library = { id = "booket.jvm.library", version = "unspecified" } booket-kotlin-library-serialization = { id = "booket.kotlin.library.serialization", version = "unspecified" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 9508c61b..8a737120 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -30,6 +30,7 @@ include( ":core:datastore:api", ":core:datastore:impl", ":core:designsystem", + ":core:di", ":core:model", ":core:network", ":core:ui", diff --git a/stability.config.conf b/stability.config.conf new file mode 100644 index 00000000..2d770ff8 --- /dev/null +++ b/stability.config.conf @@ -0,0 +1,32 @@ +// Stability Configuration for Reed-Android +// This file declares classes that should be considered stable by the Compose Compiler +// https://developer.android.com/develop/ui/compose/performance/stability/fix#configuration-file + +// Kotlin standard library +kotlin.Pair +kotlin.Triple +kotlin.Result + +// Kotlinx collections +kotlinx.collections.immutable.* + +// Kotlinx coroutines +kotlinx.coroutines.flow.StateFlow +kotlinx.coroutines.flow.Flow +kotlinx.coroutines.flow.MutableStateFlow +kotlinx.coroutines.flow.SharedFlow +kotlinx.coroutines.flow.MutableSharedFlow + +// Project model classes +com.ninecraft.booket.core.model.* +com.ninecraft.booket.core.domain.model.* + +// Circuit +com.slack.circuit.runtime.CircuitUiState +com.slack.circuit.runtime.CircuitUiEvent + +// Android architecture components +androidx.lifecycle.LiveData +androidx.lifecycle.MutableLiveData +androidx.compose.runtime.State +androidx.compose.runtime.MutableState