diff --git a/.github/workflows/android_pr.yml b/.github/workflows/android_pr.yml index 80ce761c..d1b87a07 100644 --- a/.github/workflows/android_pr.yml +++ b/.github/workflows/android_pr.yml @@ -53,7 +53,7 @@ jobs: uses: gradle/actions/setup-gradle@v4 - name: Snapshot test (shared-ui) - run: ./gradlew verifyPaparazziDebug --continue + run: ./gradlew verifyPaparazzi --continue - name: Commit and Push Images to GitHub Pages if: failure() diff --git a/.github/workflows/post-merge.yml b/.github/workflows/post-merge.yml new file mode 100644 index 00000000..03065eaf --- /dev/null +++ b/.github/workflows/post-merge.yml @@ -0,0 +1,58 @@ +name: Save Release APK + +on: + push: + branches: [ development ] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + persistReleaseApk: + env: + SENTRY_BASE_URL_ANDROID: ${{secrets.SENTRY_BASE_URL_ANDROID}} + SENTRY_AUTH_TOKEN: ${{secrets.SENTRY_AUTH_TOKEN}} + GOOGLE_SERVER_CLIENT_ID: ${{secrets.GOOGLE_SERVER_CLIENT_ID}} + MAPS_API_KEY_ANDROID: ${{secrets.MAPS_API_KEY_ANDROID}} + OPENAI_KEY: ${{secrets.OPENAI_KEY}} + STORE_FILE_BASE64: ${{secrets.SIGNING_KEY_BASE64}} + STORE_PASSWORD: ${{secrets.SIGNING_KEYSTORE_PASSWORD}} + KEY_ALIAS: ${{secrets.SIGNING_KEY_ALIAS}} + KEY_PASSWORD: ${{secrets.SIGNING_KEY_PASSWORD}} + SUPABASE_URL: ${{secrets.SUPABASE_URL}} + SUPABASE_KEY: ${{secrets.SUPABASE_KEY}} + + name: Save Release APK artifact + runs-on: macos-14 + steps: + - uses: actions/checkout@v4 + with: + lfs: true + + - uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '17' + + - name: Setup secrets.properties and Configurations/Release.xcconfig + run: ./environments.sh + + - name: Decode keystore file from secrets + run: echo "$STORE_FILE_BASE64" | base64 --decode > keystore + + - name: Inject Signing params into gradle.properties + run: | + echo STORE_FILE=keystore >> gradle.properties + echo STORE_PASSWORD="$STORE_PASSWORD" >> gradle.properties + echo KEY_PASSWORD="$KEY_PASSWORD" >> gradle.properties + echo KEY_ALIAS="$KEY_ALIAS" >> gradle.properties + cat gradle.properties + + - name: Build Android App (Release) + run: ./gradlew androidApp:app:assembleRelease + + - name: Upload APK artifact + uses: actions/upload-artifact@v4 + with: + name: apk-release + path: androidApp/app/build/outputs/apk/release/app-release.apk diff --git a/androidApp/app/build.gradle.kts b/androidApp/app/build.gradle.kts index 1cf270d4..877b4557 100644 --- a/androidApp/app/build.gradle.kts +++ b/androidApp/app/build.gradle.kts @@ -6,7 +6,7 @@ plugins { alias(libs.plugins.compose.compiler) alias(libs.plugins.kotlin.serialization) id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin") - id("io.sentry.android.gradle") version "4.14.1" + id("io.sentry.android.gradle") version "5.1.0" id("com.google.firebase.appdistribution") version "5.1.0" } @@ -16,7 +16,7 @@ android { applicationId = "com.foreverrafs.superdiary" minSdk = 28 targetSdk = 35 - versionCode = 19 + versionCode = 26 versionName = "0.0.1" val sentryBaseUrl = System.getenv("SENTRY_BASE_URL_ANDROID") ?: "" @@ -137,6 +137,7 @@ dependencies { implementation(libs.androidx.activity.compose) implementation(libs.google.material) implementation(projects.sharedUi) + implementation(projects.feature.diaryAuth) implementation(projects.core.analytics) implementation(libs.koin.android) implementation(libs.androidx.core) diff --git a/androidApp/app/src/main/kotlin/com/foreverrafs/superdiary/DiaryApp.kt b/androidApp/app/src/main/kotlin/com/foreverrafs/superdiary/DiaryApp.kt index dc093acb..01238420 100644 --- a/androidApp/app/src/main/kotlin/com/foreverrafs/superdiary/DiaryApp.kt +++ b/androidApp/app/src/main/kotlin/com/foreverrafs/superdiary/DiaryApp.kt @@ -10,11 +10,10 @@ import com.foreverrafs.superdiary.core.logging.SentryLogger import com.foreverrafs.superdiary.ui.di.compositeModule import org.koin.android.ext.koin.androidContext import org.koin.core.component.KoinComponent -import org.koin.core.component.inject import org.koin.core.context.startKoin class DiaryApp : Application(), KoinComponent { - private val androidContextProvider: AndroidContextProvider by inject() + private val androidContextProvider = AndroidContextProvider.getInstance() override fun onCreate() { super.onCreate() initializeKoin() @@ -22,36 +21,30 @@ class DiaryApp : Application(), KoinComponent { } private fun registerAndroidContextProviderCallbacks() { - registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks { - override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { - androidContextProvider.setContext(activity) - } + registerActivityLifecycleCallbacks( + object : ActivityLifecycleCallbacks { + override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) = + Unit - override fun onActivityStarted(activity: Activity) { - androidContextProvider.setContext(activity) - } + override fun onActivityStarted(activity: Activity) = Unit - // Sometimes activities will cycle between paused and resumed states - override fun onActivityResumed(activity: Activity) { - androidContextProvider.setContext(activity) - } + // Sometimes activities will cycle between paused and resumed states + override fun onActivityResumed(activity: Activity) { + androidContextProvider.setContext(activity) + } - override fun onActivityPaused(activity: Activity) { - androidContextProvider.clearContext() - } + override fun onActivityPaused(activity: Activity) { + androidContextProvider.clearContext() + } - // Cleared in onPaused but being extra cautious - override fun onActivityStopped(activity: Activity) { - androidContextProvider.clearContext() - } + override fun onActivityStopped(activity: Activity) = Unit - override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) = Unit + override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) = + Unit - // Cleared in onPaused but being extra cautious - override fun onActivityDestroyed(activity: Activity) { - androidContextProvider.clearContext() - } - }) + override fun onActivityDestroyed(activity: Activity) = Unit + }, + ) } private fun initializeKoin() { diff --git a/androidApp/app/src/main/kotlin/com/foreverrafs/superdiary/MainActivity.kt b/androidApp/app/src/main/kotlin/com/foreverrafs/superdiary/MainActivity.kt index 8a7da78c..d30e2422 100644 --- a/androidApp/app/src/main/kotlin/com/foreverrafs/superdiary/MainActivity.kt +++ b/androidApp/app/src/main/kotlin/com/foreverrafs/superdiary/MainActivity.kt @@ -8,9 +8,9 @@ import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.testTagsAsResourceId +import com.foreverrafs.superdiary.auth.register.DeeplinkContainer import com.foreverrafs.superdiary.core.logging.AggregateLogger import com.foreverrafs.superdiary.ui.App -import com.foreverrafs.superdiary.ui.feature.auth.register.DeeplinkContainer import io.github.jan.supabase.SupabaseClient import io.github.jan.supabase.auth.auth import org.koin.android.ext.android.inject diff --git a/common-test/build.gradle.kts b/common-test/build.gradle.kts index 49bbb88c..876d6e17 100644 --- a/common-test/build.gradle.kts +++ b/common-test/build.gradle.kts @@ -26,6 +26,7 @@ kotlin { implementation(libs.kotlinx.coroutines.test) implementation(libs.koin.core) implementation(projects.commonUtils) + implementation(libs.turbine) } } diff --git a/shared-ui/src/commonTest/kotlin/com/foreverrafs/superdiary/ui/Utils.kt b/common-test/src/commonMain/kotlin/com/foreverrafs/superdiary/common/coroutines/Utils.kt similarity index 82% rename from shared-ui/src/commonTest/kotlin/com/foreverrafs/superdiary/ui/Utils.kt rename to common-test/src/commonMain/kotlin/com/foreverrafs/superdiary/common/coroutines/Utils.kt index d7b08257..0dc9d22d 100644 --- a/shared-ui/src/commonTest/kotlin/com/foreverrafs/superdiary/ui/Utils.kt +++ b/common-test/src/commonMain/kotlin/com/foreverrafs/superdiary/common/coroutines/Utils.kt @@ -1,4 +1,4 @@ -package com.foreverrafs.superdiary.ui +package com.foreverrafs.superdiary.common.coroutines import app.cash.turbine.ReceiveTurbine diff --git a/core/auth/.gitignore b/core/authentication/.gitignore similarity index 100% rename from core/auth/.gitignore rename to core/authentication/.gitignore diff --git a/core/auth/build.gradle.kts b/core/authentication/build.gradle.kts similarity index 100% rename from core/auth/build.gradle.kts rename to core/authentication/build.gradle.kts index 74608a34..cdd6bbf8 100644 --- a/core/auth/build.gradle.kts +++ b/core/authentication/build.gradle.kts @@ -14,8 +14,8 @@ plugins { kotlin { androidTarget() - iosX64() jvm() + iosX64() iosArm64() iosSimulatorArm64() diff --git a/core/auth/src/androidMain/AndroidManifest.xml b/core/authentication/src/androidMain/AndroidManifest.xml similarity index 100% rename from core/auth/src/androidMain/AndroidManifest.xml rename to core/authentication/src/androidMain/AndroidManifest.xml diff --git a/core/auth/src/androidMain/kotlin/com/foreverrafs/auth/AndroidAuth.kt b/core/authentication/src/androidMain/kotlin/com/foreverrafs/auth/AndroidAuth.kt similarity index 100% rename from core/auth/src/androidMain/kotlin/com/foreverrafs/auth/AndroidAuth.kt rename to core/authentication/src/androidMain/kotlin/com/foreverrafs/auth/AndroidAuth.kt diff --git a/core/auth/src/androidMain/kotlin/com/foreverrafs/auth/AndroidBiometricAuth.kt b/core/authentication/src/androidMain/kotlin/com/foreverrafs/auth/AndroidBiometricAuth.kt similarity index 100% rename from core/auth/src/androidMain/kotlin/com/foreverrafs/auth/AndroidBiometricAuth.kt rename to core/authentication/src/androidMain/kotlin/com/foreverrafs/auth/AndroidBiometricAuth.kt diff --git a/core/auth/src/androidMain/kotlin/com/foreverrafs/auth/AndroidContextProvider.kt b/core/authentication/src/androidMain/kotlin/com/foreverrafs/auth/AndroidContextProvider.kt similarity index 68% rename from core/auth/src/androidMain/kotlin/com/foreverrafs/auth/AndroidContextProvider.kt rename to core/authentication/src/androidMain/kotlin/com/foreverrafs/auth/AndroidContextProvider.kt index bf4f89ae..775f4d87 100644 --- a/core/auth/src/androidMain/kotlin/com/foreverrafs/auth/AndroidContextProvider.kt +++ b/core/authentication/src/androidMain/kotlin/com/foreverrafs/auth/AndroidContextProvider.kt @@ -1,5 +1,6 @@ package com.foreverrafs.auth +import android.annotation.SuppressLint import android.content.Context /** @@ -11,10 +12,16 @@ import android.content.Context * This should only be used from the Application class where it can be * cleared in a structured manner */ -class AndroidContextProvider { +class AndroidContextProvider private constructor() { private var context: Context? = null fun getContext(): Context? = context + companion object { + @SuppressLint("StaticFieldLeak") + private val instance = AndroidContextProvider() + fun getInstance(): AndroidContextProvider = instance + } + fun setContext(context: Context) { this.context = context } diff --git a/core/auth/src/androidMain/kotlin/com/foreverrafs/auth/di/AuthModule.android.kt b/core/authentication/src/androidMain/kotlin/com/foreverrafs/auth/di/AuthModule.android.kt similarity index 87% rename from core/auth/src/androidMain/kotlin/com/foreverrafs/auth/di/AuthModule.android.kt rename to core/authentication/src/androidMain/kotlin/com/foreverrafs/auth/di/AuthModule.android.kt index fdca636f..91b63172 100644 --- a/core/auth/src/androidMain/kotlin/com/foreverrafs/auth/di/AuthModule.android.kt +++ b/core/authentication/src/androidMain/kotlin/com/foreverrafs/auth/di/AuthModule.android.kt @@ -8,11 +8,10 @@ import com.foreverrafs.auth.BiometricAuth import org.koin.core.module.Module import org.koin.core.module.dsl.bind import org.koin.core.module.dsl.factoryOf -import org.koin.core.module.dsl.singleOf import org.koin.dsl.module internal actual fun platformAuthModule(): Module = module { - singleOf(::AndroidContextProvider) + single { AndroidContextProvider.getInstance() } factoryOf(::AndroidAuth) { bind() } factoryOf(::AndroidBiometricAuth) { bind() } } diff --git a/core/auth/src/commonMain/kotlin/com/foreverrafs/auth/AuthApi.kt b/core/authentication/src/commonMain/kotlin/com/foreverrafs/auth/AuthApi.kt similarity index 100% rename from core/auth/src/commonMain/kotlin/com/foreverrafs/auth/AuthApi.kt rename to core/authentication/src/commonMain/kotlin/com/foreverrafs/auth/AuthApi.kt diff --git a/core/auth/src/commonMain/kotlin/com/foreverrafs/auth/BiometricAuth.kt b/core/authentication/src/commonMain/kotlin/com/foreverrafs/auth/BiometricAuth.kt similarity index 100% rename from core/auth/src/commonMain/kotlin/com/foreverrafs/auth/BiometricAuth.kt rename to core/authentication/src/commonMain/kotlin/com/foreverrafs/auth/BiometricAuth.kt diff --git a/core/auth/src/commonMain/kotlin/com/foreverrafs/auth/DefaultSupabaseAuth.kt b/core/authentication/src/commonMain/kotlin/com/foreverrafs/auth/DefaultSupabaseAuth.kt similarity index 100% rename from core/auth/src/commonMain/kotlin/com/foreverrafs/auth/DefaultSupabaseAuth.kt rename to core/authentication/src/commonMain/kotlin/com/foreverrafs/auth/DefaultSupabaseAuth.kt diff --git a/core/auth/src/commonMain/kotlin/com/foreverrafs/auth/di/AuthModule.kt b/core/authentication/src/commonMain/kotlin/com/foreverrafs/auth/di/AuthModule.kt similarity index 100% rename from core/auth/src/commonMain/kotlin/com/foreverrafs/auth/di/AuthModule.kt rename to core/authentication/src/commonMain/kotlin/com/foreverrafs/auth/di/AuthModule.kt diff --git a/core/auth/src/commonMain/kotlin/com/foreverrafs/auth/di/SupabaseModule.kt b/core/authentication/src/commonMain/kotlin/com/foreverrafs/auth/di/SupabaseModule.kt similarity index 100% rename from core/auth/src/commonMain/kotlin/com/foreverrafs/auth/di/SupabaseModule.kt rename to core/authentication/src/commonMain/kotlin/com/foreverrafs/auth/di/SupabaseModule.kt diff --git a/core/auth/src/commonMain/kotlin/com/foreverrafs/auth/model/SessionInfo.kt b/core/authentication/src/commonMain/kotlin/com/foreverrafs/auth/model/SessionInfo.kt similarity index 100% rename from core/auth/src/commonMain/kotlin/com/foreverrafs/auth/model/SessionInfo.kt rename to core/authentication/src/commonMain/kotlin/com/foreverrafs/auth/model/SessionInfo.kt diff --git a/core/auth/src/commonMain/kotlin/com/foreverrafs/auth/model/UserInfo.kt b/core/authentication/src/commonMain/kotlin/com/foreverrafs/auth/model/UserInfo.kt similarity index 100% rename from core/auth/src/commonMain/kotlin/com/foreverrafs/auth/model/UserInfo.kt rename to core/authentication/src/commonMain/kotlin/com/foreverrafs/auth/model/UserInfo.kt diff --git a/core/auth/src/iosMain/kotlin/com/foreverrafs/auth/AppleAuth.kt b/core/authentication/src/iosMain/kotlin/com/foreverrafs/auth/AppleAuth.kt similarity index 100% rename from core/auth/src/iosMain/kotlin/com/foreverrafs/auth/AppleAuth.kt rename to core/authentication/src/iosMain/kotlin/com/foreverrafs/auth/AppleAuth.kt diff --git a/core/auth/src/iosMain/kotlin/com/foreverrafs/auth/GoogleTokenProvider.kt b/core/authentication/src/iosMain/kotlin/com/foreverrafs/auth/GoogleTokenProvider.kt similarity index 100% rename from core/auth/src/iosMain/kotlin/com/foreverrafs/auth/GoogleTokenProvider.kt rename to core/authentication/src/iosMain/kotlin/com/foreverrafs/auth/GoogleTokenProvider.kt diff --git a/core/auth/src/iosMain/kotlin/com/foreverrafs/auth/di/AuthModule.ios.kt b/core/authentication/src/iosMain/kotlin/com/foreverrafs/auth/di/AuthModule.ios.kt similarity index 100% rename from core/auth/src/iosMain/kotlin/com/foreverrafs/auth/di/AuthModule.ios.kt rename to core/authentication/src/iosMain/kotlin/com/foreverrafs/auth/di/AuthModule.ios.kt diff --git a/core/auth/src/jvmMain/kotlin/com/foreverrafs/auth/di/AuthModule.jvm.kt b/core/authentication/src/jvmMain/kotlin/com/foreverrafs/auth/di/AuthModule.jvm.kt similarity index 100% rename from core/auth/src/jvmMain/kotlin/com/foreverrafs/auth/di/AuthModule.jvm.kt rename to core/authentication/src/jvmMain/kotlin/com/foreverrafs/auth/di/AuthModule.jvm.kt diff --git a/design-system/build.gradle.kts b/design-system/build.gradle.kts index be0656ec..f61aad39 100644 --- a/design-system/build.gradle.kts +++ b/design-system/build.gradle.kts @@ -68,6 +68,7 @@ kotlin { androidMain { dependencies { implementation(libs.compose.ui.tooling) + implementation(libs.androidx.activity.compose) implementation(libs.kotlinx.coroutines.android) implementation(libs.google.maps.compose) } diff --git a/design-system/src/androidMain/kotlin/com/foreverrafs/superdiary/design/components/BackHandler.android.kt b/design-system/src/androidMain/kotlin/com/foreverrafs/superdiary/design/components/BackHandler.android.kt new file mode 100644 index 00000000..90524ba5 --- /dev/null +++ b/design-system/src/androidMain/kotlin/com/foreverrafs/superdiary/design/components/BackHandler.android.kt @@ -0,0 +1,11 @@ +package com.foreverrafs.superdiary.design.components + +import androidx.compose.runtime.Composable + +@Composable +actual fun BackHandler(onBack: () -> Unit) { + androidx.activity.compose.BackHandler( + enabled = true, + onBack = onBack, + ) +} diff --git a/design-system/src/androidMain/kotlin/com/foreverrafs/superdiary/design/components/SuperDiaryDialog.android.kt b/design-system/src/androidMain/kotlin/com/foreverrafs/superdiary/design/components/SuperDiaryDialog.android.kt new file mode 100644 index 00000000..8d38d9bb --- /dev/null +++ b/design-system/src/androidMain/kotlin/com/foreverrafs/superdiary/design/components/SuperDiaryDialog.android.kt @@ -0,0 +1,60 @@ +package com.foreverrafs.superdiary.design.components + +import androidx.compose.runtime.Composable +import org.jetbrains.compose.resources.stringResource +import superdiary.design_system.generated.resources.Res +import superdiary.design_system.generated.resources.confirm_delete_diary_dialog_message +import superdiary.design_system.generated.resources.confirm_delete_diary_dialog_title +import superdiary.design_system.generated.resources.confirm_delete_diary_negative_button +import superdiary.design_system.generated.resources.confirm_delete_diary_positive_button +import superdiary.design_system.generated.resources.confirm_save_diary_dialog_message +import superdiary.design_system.generated.resources.confirm_save_diary_dialog_title +import superdiary.design_system.generated.resources.confirm_save_diary_negative_button +import superdiary.design_system.generated.resources.confirm_save_diary_positive_button + +@Composable +actual fun ConfirmSaveDialog( + onDismiss: () -> Unit, + onConfirm: () -> Unit, + onDismissRequest: () -> Unit, +) { + BasicMaterialDialog( + onNegativeButton = onDismiss, + onPositiveButton = onConfirm, + title = stringResource(Res.string.confirm_save_diary_dialog_title), + message = stringResource(Res.string.confirm_save_diary_dialog_message), + positiveButtonText = stringResource(Res.string.confirm_save_diary_positive_button), + negativeButtonText = stringResource(Res.string.confirm_save_diary_negative_button), + onDismissRequest = onDismissRequest, + ) +} + +@Composable +actual fun ConfirmDeleteDialog(onDismiss: () -> Unit, onConfirm: () -> Unit) { + BasicMaterialDialog( + onNegativeButton = onDismiss, + onPositiveButton = onConfirm, + title = stringResource(Res.string.confirm_delete_diary_dialog_title), + message = stringResource(Res.string.confirm_delete_diary_dialog_message), + negativeButtonText = stringResource(Res.string.confirm_delete_diary_negative_button), + positiveButtonText = stringResource(Res.string.confirm_delete_diary_positive_button), + onDismissRequest = onDismiss, + ) +} + +@Composable +actual fun ConfirmBiometricAuthDialog( + onDismiss: () -> Unit, + onEnableBiometric: () -> Unit, + onDismissRequest: () -> Unit, +) { + BasicMaterialDialog( + onNegativeButton = onDismiss, + onPositiveButton = onEnableBiometric, + title = "Biometric Authentication", + message = "Do you want to enable biometric authentication?", + negativeButtonText = "No", + positiveButtonText = "Yes", + onDismissRequest = onDismissRequest, + ) +} diff --git a/design-system/src/commonMain/composeResources/values/strings.xml b/design-system/src/commonMain/composeResources/values/strings.xml index f48c5fe8..70e8985d 100644 --- a/design-system/src/commonMain/composeResources/values/strings.xml +++ b/design-system/src/commonMain/composeResources/values/strings.xml @@ -8,13 +8,9 @@ Confirm Save Do you want to save the entry? Save + Cancel Don't save - Confirm logout - Are you sure you want to logout? - Cancel - Logout - Diary AI: Add entry diff --git a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/BackHandler.kt b/design-system/src/commonMain/kotlin/com/foreverrafs/superdiary/design/components/BackHandler.kt similarity index 65% rename from shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/BackHandler.kt rename to design-system/src/commonMain/kotlin/com/foreverrafs/superdiary/design/components/BackHandler.kt index 56d33c14..3a3d0008 100644 --- a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/BackHandler.kt +++ b/design-system/src/commonMain/kotlin/com/foreverrafs/superdiary/design/components/BackHandler.kt @@ -1,4 +1,4 @@ -package com.foreverrafs.superdiary.ui +package com.foreverrafs.superdiary.design.components import androidx.compose.runtime.Composable diff --git a/design-system/src/commonMain/kotlin/com/foreverrafs/superdiary/design/components/SuperDiaryDialog.kt b/design-system/src/commonMain/kotlin/com/foreverrafs/superdiary/design/components/SuperDiaryDialog.kt index 94fb396c..6a7e5c6e 100644 --- a/design-system/src/commonMain/kotlin/com/foreverrafs/superdiary/design/components/SuperDiaryDialog.kt +++ b/design-system/src/commonMain/kotlin/com/foreverrafs/superdiary/design/components/SuperDiaryDialog.kt @@ -25,135 +25,29 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.window.DialogProperties -import org.jetbrains.compose.resources.stringResource -import superdiary.design_system.generated.resources.Res -import superdiary.design_system.generated.resources.confirm_delete_diary_dialog_message -import superdiary.design_system.generated.resources.confirm_delete_diary_dialog_title -import superdiary.design_system.generated.resources.confirm_delete_diary_negative_button -import superdiary.design_system.generated.resources.confirm_delete_diary_positive_button -import superdiary.design_system.generated.resources.confirm_logout_dialog_cancel_button -import superdiary.design_system.generated.resources.confirm_logout_dialog_confirm_button -import superdiary.design_system.generated.resources.confirm_logout_dialog_message -import superdiary.design_system.generated.resources.confirm_logout_dialog_title -import superdiary.design_system.generated.resources.confirm_save_diary_dialog_message -import superdiary.design_system.generated.resources.confirm_save_diary_dialog_title -import superdiary.design_system.generated.resources.confirm_save_diary_negative_button -import superdiary.design_system.generated.resources.confirm_save_diary_positive_button @Composable -fun ConfirmDeleteDialog( +expect fun ConfirmDeleteDialog( onDismiss: () -> Unit, onConfirm: () -> Unit, -) { - AlertDialog( - onDismissRequest = onDismiss, - title = { - Text( - text = stringResource(Res.string.confirm_delete_diary_dialog_title), - style = MaterialTheme.typography.titleMedium, - ) - }, - text = { - Text( - text = stringResource(Res.string.confirm_delete_diary_dialog_message), - style = MaterialTheme.typography.bodyMedium, - ) - }, - confirmButton = { - TextButton(onClick = onConfirm) { - Text( - text = stringResource(Res.string.confirm_delete_diary_positive_button), - style = MaterialTheme.typography.labelMedium, - ) - } - }, - dismissButton = { - TextButton(onClick = onDismiss) { - Text( - text = stringResource(Res.string.confirm_delete_diary_negative_button), - style = MaterialTheme.typography.labelMedium, - color = MaterialTheme.colorScheme.error, - ) - } - }, - containerColor = MaterialTheme.colorScheme.primaryContainer, - ) -} +) @Composable -fun ConfirmSaveDialog( +expect fun ConfirmSaveDialog( onDismiss: () -> Unit, onConfirm: () -> Unit, onDismissRequest: () -> Unit, -) { - BasicSuperDiaryDialog( - onNegativeButton = onDismiss, - onPositiveButton = onConfirm, - title = stringResource(Res.string.confirm_save_diary_dialog_title), - message = stringResource(Res.string.confirm_save_diary_dialog_message), - positiveButtonText = stringResource(Res.string.confirm_save_diary_positive_button), - negativeButtonText = stringResource(Res.string.confirm_save_diary_negative_button), - onDismissRequest = onDismissRequest, - ) -} - -@Composable -fun BiometricAuthErrorDialog( - onExitApp: () -> Unit, - onTryAgain: () -> Unit, - onDismissRequest: () -> Unit, -) { - BasicSuperDiaryDialog( - onNegativeButton = onExitApp, - onPositiveButton = onTryAgain, - title = "Authentication failed", - message = "Would you like to try biometric authentication again?", - positiveButtonText = "Try again", - negativeButtonText = "Exit", - onDismissRequest = onDismissRequest, - properties = DialogProperties( - dismissOnBackPress = false, - dismissOnClickOutside = false, - ), - ) -} +) @Composable -fun ConfirmLogoutDialog( - onLogout: () -> Unit, - onDismiss: () -> Unit, - onDismissRequest: () -> Unit, -) { - BasicSuperDiaryDialog( - onNegativeButton = onLogout, - onPositiveButton = onDismiss, - title = stringResource(Res.string.confirm_logout_dialog_title), - message = stringResource(Res.string.confirm_logout_dialog_message), - negativeButtonText = stringResource(Res.string.confirm_logout_dialog_confirm_button), - positiveButtonText = stringResource(Res.string.confirm_logout_dialog_cancel_button), - onDismissRequest = onDismissRequest, - ) -} - -@Composable -fun ConfirmBiometricAuthDialog( +expect fun ConfirmBiometricAuthDialog( onDismiss: () -> Unit, onEnableBiometric: () -> Unit, onDismissRequest: () -> Unit, -) { - BasicSuperDiaryDialog( - onNegativeButton = onDismiss, - onPositiveButton = onEnableBiometric, - title = "Biometric Authentication", - message = "Do you want to enable biometric authentication?", - negativeButtonText = "No", - positiveButtonText = "Yes", - onDismissRequest = onDismissRequest, - ) -} +) @Composable -private fun BasicSuperDiaryDialog( +fun BasicMaterialDialog( title: String, message: String, negativeButtonText: String, @@ -228,7 +122,6 @@ fun LocationRationaleDialog( .padding(top = 35.dp) .height(70.dp) .fillMaxWidth(), - ) Column( diff --git a/shared-ui/src/iosMain/kotlin/com/foreverrafs/superdiary/ui/SuperDiaryBackPressHandler.ios.kt b/design-system/src/iosMain/kotlin/com/foreverrafs/superdiary/design/components/BackHandler.ios.kt similarity index 66% rename from shared-ui/src/iosMain/kotlin/com/foreverrafs/superdiary/ui/SuperDiaryBackPressHandler.ios.kt rename to design-system/src/iosMain/kotlin/com/foreverrafs/superdiary/design/components/BackHandler.ios.kt index df23ee1b..9133f459 100644 --- a/shared-ui/src/iosMain/kotlin/com/foreverrafs/superdiary/ui/SuperDiaryBackPressHandler.ios.kt +++ b/design-system/src/iosMain/kotlin/com/foreverrafs/superdiary/design/components/BackHandler.ios.kt @@ -1,4 +1,4 @@ -package com.foreverrafs.superdiary.ui +package com.foreverrafs.superdiary.design.components import androidx.compose.runtime.Composable diff --git a/design-system/src/iosMain/kotlin/com/foreverrafs/superdiary/design/components/SuperDiaryDialog.ios.kt b/design-system/src/iosMain/kotlin/com/foreverrafs/superdiary/design/components/SuperDiaryDialog.ios.kt new file mode 100644 index 00000000..5668c8fc --- /dev/null +++ b/design-system/src/iosMain/kotlin/com/foreverrafs/superdiary/design/components/SuperDiaryDialog.ios.kt @@ -0,0 +1,121 @@ +package com.foreverrafs.superdiary.design.components + +import androidx.compose.runtime.Composable +import org.jetbrains.compose.resources.stringResource +import platform.UIKit.UIAlertAction +import platform.UIKit.UIAlertActionStyleCancel +import platform.UIKit.UIAlertActionStyleDefault +import platform.UIKit.UIAlertActionStyleDestructive +import platform.UIKit.UIAlertController +import platform.UIKit.UIAlertControllerStyleAlert +import platform.UIKit.UIApplication +import superdiary.design_system.generated.resources.Res +import superdiary.design_system.generated.resources.confirm_delete_diary_dialog_message +import superdiary.design_system.generated.resources.confirm_delete_diary_dialog_title +import superdiary.design_system.generated.resources.confirm_delete_diary_negative_button +import superdiary.design_system.generated.resources.confirm_delete_diary_positive_button +import superdiary.design_system.generated.resources.confirm_save_diary_cancel_button +import superdiary.design_system.generated.resources.confirm_save_diary_dialog_message +import superdiary.design_system.generated.resources.confirm_save_diary_dialog_title +import superdiary.design_system.generated.resources.confirm_save_diary_negative_button +import superdiary.design_system.generated.resources.confirm_save_diary_positive_button + +@Composable +actual fun ConfirmSaveDialog( + onDismiss: () -> Unit, + onConfirm: () -> Unit, + onDismissRequest: () -> Unit, +) { + BasicCupertinoDialog( + onNegativeButton = onDismiss, + onPositiveButton = onConfirm, + title = stringResource(Res.string.confirm_save_diary_dialog_title), + message = stringResource(Res.string.confirm_save_diary_dialog_message), + positiveButtonText = stringResource(Res.string.confirm_save_diary_positive_button), + negativeButtonText = stringResource(Res.string.confirm_save_diary_negative_button), + cancelButtonText = stringResource(Res.string.confirm_save_diary_cancel_button), + onDismissRequest = onDismissRequest, + ) +} + +@Composable +fun BasicCupertinoDialog( + title: String, + message: String, + positiveButtonText: String, + negativeButtonText: String, + onPositiveButton: () -> Unit, + onNegativeButton: () -> Unit, + onDismissRequest: () -> Unit, + cancelButtonText: String? = null, +) { + val alert = UIAlertController.alertControllerWithTitle( + title = title, + message = message, + preferredStyle = UIAlertControllerStyleAlert, + ) + + alert.addAction( + UIAlertAction.actionWithTitle( + title = positiveButtonText, + style = UIAlertActionStyleDefault, + handler = { + onPositiveButton() + }, + ), + ) + + alert.addAction( + UIAlertAction.actionWithTitle( + title = negativeButtonText, + style = UIAlertActionStyleDestructive, + handler = { + onNegativeButton() + }, + ), + ) + cancelButtonText?.let { + alert.addAction( + UIAlertAction.actionWithTitle( + title = it, + style = UIAlertActionStyleCancel, + handler = { onDismissRequest() }, + ), + ) + } + + // Get the top-most view controller + val keyWindow = UIApplication.sharedApplication.keyWindow + val rootViewController = keyWindow?.rootViewController + rootViewController?.presentViewController(alert, animated = true, completion = null) +} + +@Composable +actual fun ConfirmDeleteDialog(onDismiss: () -> Unit, onConfirm: () -> Unit) { + BasicCupertinoDialog( + onNegativeButton = onDismiss, + onPositiveButton = onConfirm, + title = stringResource(Res.string.confirm_delete_diary_dialog_title), + message = stringResource(Res.string.confirm_delete_diary_dialog_message), + negativeButtonText = stringResource(Res.string.confirm_delete_diary_negative_button), + positiveButtonText = stringResource(Res.string.confirm_delete_diary_positive_button), + onDismissRequest = onDismiss, + ) +} + +@Composable +actual fun ConfirmBiometricAuthDialog( + onDismiss: () -> Unit, + onEnableBiometric: () -> Unit, + onDismissRequest: () -> Unit, +) { + BasicCupertinoDialog( + onNegativeButton = onDismiss, + onPositiveButton = onEnableBiometric, + title = "Biometric Authentication", + message = "Do you want to enable biometric authentication?", + negativeButtonText = "No", + positiveButtonText = "Yes", + onDismissRequest = onDismissRequest, + ) +} diff --git a/shared-ui/src/jvmMain/kotlin/com/foreverrafs/superdiary/ui/SuperDiaryBackPressHandler.jvm.kt b/design-system/src/jvmMain/kotlin/com/foreverrafs/superdiary/design/components/BackHandler.jvm.kt similarity index 66% rename from shared-ui/src/jvmMain/kotlin/com/foreverrafs/superdiary/ui/SuperDiaryBackPressHandler.jvm.kt rename to design-system/src/jvmMain/kotlin/com/foreverrafs/superdiary/design/components/BackHandler.jvm.kt index df23ee1b..9133f459 100644 --- a/shared-ui/src/jvmMain/kotlin/com/foreverrafs/superdiary/ui/SuperDiaryBackPressHandler.jvm.kt +++ b/design-system/src/jvmMain/kotlin/com/foreverrafs/superdiary/design/components/BackHandler.jvm.kt @@ -1,4 +1,4 @@ -package com.foreverrafs.superdiary.ui +package com.foreverrafs.superdiary.design.components import androidx.compose.runtime.Composable diff --git a/design-system/src/jvmMain/kotlin/com/foreverrafs/superdiary/design/components/SuperDiaryDialog.jvm.kt b/design-system/src/jvmMain/kotlin/com/foreverrafs/superdiary/design/components/SuperDiaryDialog.jvm.kt new file mode 100644 index 00000000..8d38d9bb --- /dev/null +++ b/design-system/src/jvmMain/kotlin/com/foreverrafs/superdiary/design/components/SuperDiaryDialog.jvm.kt @@ -0,0 +1,60 @@ +package com.foreverrafs.superdiary.design.components + +import androidx.compose.runtime.Composable +import org.jetbrains.compose.resources.stringResource +import superdiary.design_system.generated.resources.Res +import superdiary.design_system.generated.resources.confirm_delete_diary_dialog_message +import superdiary.design_system.generated.resources.confirm_delete_diary_dialog_title +import superdiary.design_system.generated.resources.confirm_delete_diary_negative_button +import superdiary.design_system.generated.resources.confirm_delete_diary_positive_button +import superdiary.design_system.generated.resources.confirm_save_diary_dialog_message +import superdiary.design_system.generated.resources.confirm_save_diary_dialog_title +import superdiary.design_system.generated.resources.confirm_save_diary_negative_button +import superdiary.design_system.generated.resources.confirm_save_diary_positive_button + +@Composable +actual fun ConfirmSaveDialog( + onDismiss: () -> Unit, + onConfirm: () -> Unit, + onDismissRequest: () -> Unit, +) { + BasicMaterialDialog( + onNegativeButton = onDismiss, + onPositiveButton = onConfirm, + title = stringResource(Res.string.confirm_save_diary_dialog_title), + message = stringResource(Res.string.confirm_save_diary_dialog_message), + positiveButtonText = stringResource(Res.string.confirm_save_diary_positive_button), + negativeButtonText = stringResource(Res.string.confirm_save_diary_negative_button), + onDismissRequest = onDismissRequest, + ) +} + +@Composable +actual fun ConfirmDeleteDialog(onDismiss: () -> Unit, onConfirm: () -> Unit) { + BasicMaterialDialog( + onNegativeButton = onDismiss, + onPositiveButton = onConfirm, + title = stringResource(Res.string.confirm_delete_diary_dialog_title), + message = stringResource(Res.string.confirm_delete_diary_dialog_message), + negativeButtonText = stringResource(Res.string.confirm_delete_diary_negative_button), + positiveButtonText = stringResource(Res.string.confirm_delete_diary_positive_button), + onDismissRequest = onDismiss, + ) +} + +@Composable +actual fun ConfirmBiometricAuthDialog( + onDismiss: () -> Unit, + onEnableBiometric: () -> Unit, + onDismissRequest: () -> Unit, +) { + BasicMaterialDialog( + onNegativeButton = onDismiss, + onPositiveButton = onEnableBiometric, + title = "Biometric Authentication", + message = "Do you want to enable biometric authentication?", + negativeButtonText = "No", + positiveButtonText = "Yes", + onDismissRequest = onDismissRequest, + ) +} diff --git a/feature/diary-auth/build.gradle.kts b/feature/diary-auth/build.gradle.kts new file mode 100644 index 00000000..97b81534 --- /dev/null +++ b/feature/diary-auth/build.gradle.kts @@ -0,0 +1,73 @@ +@file:Suppress("UnusedPrivateProperty") + +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.compose.multiplatform) + alias(libs.plugins.compose.compiler) + alias(libs.plugins.mokkery) + alias(libs.plugins.kotlin.parcelize) + alias(libs.plugins.paparazzi) +} + +@OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class) +kotlin { + androidTarget() + + jvm() + iosArm64() + iosSimulatorArm64() + + sourceSets { + androidUnitTest.dependencies { + implementation(libs.google.testparameterinjector) + implementation(projects.commonTest) + } + + commonMain.dependencies { + implementation(libs.kotlinx.coroutines.core) + implementation(libs.koin.core) + implementation(compose.foundation) + implementation(compose.material3) + implementation(compose.components.resources) + implementation(compose.components.uiToolingPreview) + implementation(compose.materialIconsExtended) + implementation(projects.core.logging) + implementation("org.jetbrains.androidx.core:core-uri:1.1.0-alpha02") + implementation(libs.kotlinx.coroutines.test) + implementation(libs.koin.compose) + implementation(libs.koin.compose.viewmodel) + implementation(libs.kotlin.datetime) + implementation(projects.core.authentication) + implementation(projects.commonUtils) + implementation(projects.sharedData) + implementation("org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose:2.8.4") + implementation(projects.designSystem) + } + + commonTest.dependencies { + implementation(kotlin("test")) + implementation(libs.junit) + implementation(libs.koin.test) + implementation(projects.commonTest) + implementation(libs.kotlinx.coroutines.test) + implementation(libs.turbine) + implementation(libs.assertk.common) + } + } +} + +android { + namespace = "com.foreverrafs.superdiary.auth" + compileSdk = libs.versions.compileSdk.get().toInt() + + defaultConfig { + minSdk = libs.versions.minimumSdk.get().toInt() + } + + compileOptions { + targetCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.VERSION_17 + } +} diff --git a/shared-ui/src/androidUnitTest/kotlin/com/foreverrafs/superdiary/ui/LoginScreenSnapshotTest.kt b/feature/diary-auth/src/androidUnitTest/kotlin/com/foreverrafs/superdiary/auth/LoginScreenSnapshotTest.kt similarity index 90% rename from shared-ui/src/androidUnitTest/kotlin/com/foreverrafs/superdiary/ui/LoginScreenSnapshotTest.kt rename to feature/diary-auth/src/androidUnitTest/kotlin/com/foreverrafs/superdiary/auth/LoginScreenSnapshotTest.kt index 1f9cae8b..7119c8b8 100644 --- a/shared-ui/src/androidUnitTest/kotlin/com/foreverrafs/superdiary/ui/LoginScreenSnapshotTest.kt +++ b/feature/diary-auth/src/androidUnitTest/kotlin/com/foreverrafs/superdiary/auth/LoginScreenSnapshotTest.kt @@ -1,11 +1,11 @@ -package com.foreverrafs.superdiary.ui +package com.foreverrafs.superdiary.auth import app.cash.paparazzi.Paparazzi import com.android.ide.common.rendering.api.SessionParams import com.foreverrafs.common.paparazzi.SnapshotDevice +import com.foreverrafs.superdiary.auth.login.screen.LoginScreenContent +import com.foreverrafs.superdiary.auth.login.screen.LoginViewState import com.foreverrafs.superdiary.design.style.SuperDiaryPreviewTheme -import com.foreverrafs.superdiary.ui.feature.auth.login.screen.LoginScreenContent -import com.foreverrafs.superdiary.ui.feature.auth.login.screen.LoginViewState import com.google.testing.junit.testparameterinjector.TestParameter import com.google.testing.junit.testparameterinjector.TestParameterInjector import org.junit.Rule diff --git a/shared-ui/src/androidUnitTest/kotlin/com/foreverrafs/superdiary/ui/SendPasswordResetScreenSnapshotTest.kt b/feature/diary-auth/src/androidUnitTest/kotlin/com/foreverrafs/superdiary/auth/SendPasswordResetScreenSnapshotTest.kt similarity index 94% rename from shared-ui/src/androidUnitTest/kotlin/com/foreverrafs/superdiary/ui/SendPasswordResetScreenSnapshotTest.kt rename to feature/diary-auth/src/androidUnitTest/kotlin/com/foreverrafs/superdiary/auth/SendPasswordResetScreenSnapshotTest.kt index 60d958b1..cfcc74b9 100644 --- a/shared-ui/src/androidUnitTest/kotlin/com/foreverrafs/superdiary/ui/SendPasswordResetScreenSnapshotTest.kt +++ b/feature/diary-auth/src/androidUnitTest/kotlin/com/foreverrafs/superdiary/auth/SendPasswordResetScreenSnapshotTest.kt @@ -1,11 +1,11 @@ -package com.foreverrafs.superdiary.ui +package com.foreverrafs.superdiary.auth import app.cash.paparazzi.Paparazzi import com.android.ide.common.rendering.api.SessionParams import com.foreverrafs.common.paparazzi.SnapshotDevice +import com.foreverrafs.superdiary.auth.reset.PasswordResetViewState +import com.foreverrafs.superdiary.auth.reset.SendPasswordResetEmailScreenContent import com.foreverrafs.superdiary.design.style.SuperDiaryPreviewTheme -import com.foreverrafs.superdiary.ui.feature.auth.reset.PasswordResetViewState -import com.foreverrafs.superdiary.ui.feature.auth.reset.SendPasswordResetEmailScreenContent import com.google.testing.junit.testparameterinjector.TestParameter import com.google.testing.junit.testparameterinjector.TestParameterInjector import org.junit.Rule diff --git a/shared-ui/src/commonMain/composeResources/drawable/google_icon.png b/feature/diary-auth/src/commonMain/composeResources/drawable/google_icon.png similarity index 100% rename from shared-ui/src/commonMain/composeResources/drawable/google_icon.png rename to feature/diary-auth/src/commonMain/composeResources/drawable/google_icon.png diff --git a/feature/diary-auth/src/commonMain/composeResources/drawable/logo.png b/feature/diary-auth/src/commonMain/composeResources/drawable/logo.png new file mode 100644 index 00000000..a4c1ccd1 Binary files /dev/null and b/feature/diary-auth/src/commonMain/composeResources/drawable/logo.png differ diff --git a/feature/diary-auth/src/commonMain/composeResources/values/strings.xml b/feature/diary-auth/src/commonMain/composeResources/values/strings.xml new file mode 100644 index 00000000..5a560077 --- /dev/null +++ b/feature/diary-auth/src/commonMain/composeResources/values/strings.xml @@ -0,0 +1,19 @@ + + Open email + Reset password + Reset your password + Continue with Google + Username + Password + Login + Email + Welcome back! + Create an account + Don't have an account yet? + Register + + Authentication failed + Would you like to try biometric authentication again? + Try again + Exit + diff --git a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/login/BiometricLoginScreenViewModel.kt b/feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/login/BiometricLoginScreenViewModel.kt similarity index 98% rename from shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/login/BiometricLoginScreenViewModel.kt rename to feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/login/BiometricLoginScreenViewModel.kt index 1c8d5821..df23ebac 100644 --- a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/login/BiometricLoginScreenViewModel.kt +++ b/feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/login/BiometricLoginScreenViewModel.kt @@ -1,4 +1,4 @@ -package com.foreverrafs.superdiary.ui.feature.auth.login +package com.foreverrafs.superdiary.auth.login import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope diff --git a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/login/LoginScreenViewModel.kt b/feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/login/LoginScreenViewModel.kt similarity index 94% rename from shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/login/LoginScreenViewModel.kt rename to feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/login/LoginScreenViewModel.kt index 6e132635..010b843a 100644 --- a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/login/LoginScreenViewModel.kt +++ b/feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/login/LoginScreenViewModel.kt @@ -1,10 +1,10 @@ -package com.foreverrafs.superdiary.ui.feature.auth.login +package com.foreverrafs.superdiary.auth.login import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.foreverrafs.auth.AuthApi +import com.foreverrafs.superdiary.auth.login.screen.LoginViewState import com.foreverrafs.superdiary.common.utils.AppCoroutineDispatchers -import com.foreverrafs.superdiary.ui.feature.auth.login.screen.LoginViewState import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update diff --git a/feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/login/screen/BiometricAuthErrorDialog.kt b/feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/login/screen/BiometricAuthErrorDialog.kt new file mode 100644 index 00000000..8934990d --- /dev/null +++ b/feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/login/screen/BiometricAuthErrorDialog.kt @@ -0,0 +1,32 @@ +package com.foreverrafs.superdiary.auth.login.screen + +import androidx.compose.runtime.Composable +import androidx.compose.ui.window.DialogProperties +import com.foreverrafs.superdiary.design.components.BasicMaterialDialog +import org.jetbrains.compose.resources.stringResource +import superdiary.feature.diary_auth.generated.resources.Res +import superdiary.feature.diary_auth.generated.resources.dialog_biometric_auth_error_message +import superdiary.feature.diary_auth.generated.resources.dialog_biometric_auth_error_negative_btn_text +import superdiary.feature.diary_auth.generated.resources.dialog_biometric_auth_error_positive_btn_text +import superdiary.feature.diary_auth.generated.resources.dialog_biometric_auth_error_title + +@Composable +fun BiometricAuthErrorDialog( + onExitApp: () -> Unit, + onTryAgain: () -> Unit, + onDismissRequest: () -> Unit, +) { + BasicMaterialDialog( + onNegativeButton = onExitApp, + onPositiveButton = onTryAgain, + title = stringResource(Res.string.dialog_biometric_auth_error_title), + message = stringResource(Res.string.dialog_biometric_auth_error_message), + positiveButtonText = stringResource(Res.string.dialog_biometric_auth_error_positive_btn_text), + negativeButtonText = stringResource(Res.string.dialog_biometric_auth_error_negative_btn_text), + onDismissRequest = onDismissRequest, + properties = DialogProperties( + dismissOnBackPress = false, + dismissOnClickOutside = false, + ), + ) +} diff --git a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/login/screen/BiometricLoginScreen.kt b/feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/login/screen/BiometricLoginScreen.kt similarity index 91% rename from shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/login/screen/BiometricLoginScreen.kt rename to feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/login/screen/BiometricLoginScreen.kt index 1f70c67b..57c7dc92 100644 --- a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/login/screen/BiometricLoginScreen.kt +++ b/feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/login/screen/BiometricLoginScreen.kt @@ -1,4 +1,4 @@ -package com.foreverrafs.superdiary.ui.feature.auth.login.screen +package com.foreverrafs.superdiary.auth.login.screen import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -22,9 +22,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.foreverrafs.superdiary.design.components.BiometricAuthErrorDialog -import com.foreverrafs.superdiary.ui.feature.auth.login.BiometricLoginScreenState -import com.foreverrafs.superdiary.ui.feature.auth.login.BiometricLoginScreenViewModel +import com.foreverrafs.superdiary.auth.login.BiometricLoginScreenState +import com.foreverrafs.superdiary.auth.login.BiometricLoginScreenViewModel import org.koin.compose.viewmodel.koinViewModel @Composable diff --git a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/login/screen/LoginScreen.kt b/feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/login/screen/LoginScreen.kt similarity index 87% rename from shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/login/screen/LoginScreen.kt rename to feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/login/screen/LoginScreen.kt index 93bc6e8a..5b7a80d4 100644 --- a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/login/screen/LoginScreen.kt +++ b/feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/login/screen/LoginScreen.kt @@ -1,10 +1,10 @@ -package com.foreverrafs.superdiary.ui.feature.auth.login.screen +package com.foreverrafs.superdiary.auth.login.screen import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.foreverrafs.auth.model.UserInfo -import com.foreverrafs.superdiary.ui.feature.auth.login.LoginScreenViewModel +import com.foreverrafs.superdiary.auth.login.LoginScreenViewModel import org.koin.compose.viewmodel.koinViewModel @Composable diff --git a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/login/screen/LoginScreenContent.kt b/feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/login/screen/LoginScreenContent.kt similarity index 93% rename from shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/login/screen/LoginScreenContent.kt rename to feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/login/screen/LoginScreenContent.kt index 72b92807..2f1c9d30 100644 --- a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/login/screen/LoginScreenContent.kt +++ b/feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/login/screen/LoginScreenContent.kt @@ -1,4 +1,4 @@ -package com.foreverrafs.superdiary.ui.feature.auth.login.screen +package com.foreverrafs.superdiary.auth.login.screen import androidx.compose.foundation.Image import androidx.compose.foundation.clickable @@ -48,16 +48,16 @@ import com.foreverrafs.superdiary.design.style.SuperDiaryTheme import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.ui.tooling.preview.Preview -import superdiary.shared_ui.generated.resources.Res -import superdiary.shared_ui.generated.resources.google_icon -import superdiary.shared_ui.generated.resources.label_google_button -import superdiary.shared_ui.generated.resources.label_login -import superdiary.shared_ui.generated.resources.label_login_title -import superdiary.shared_ui.generated.resources.label_password -import superdiary.shared_ui.generated.resources.label_register -import superdiary.shared_ui.generated.resources.label_register_message -import superdiary.shared_ui.generated.resources.label_username -import superdiary.shared_ui.generated.resources.logo +import superdiary.feature.diary_auth.generated.resources.Res +import superdiary.feature.diary_auth.generated.resources.google_icon +import superdiary.feature.diary_auth.generated.resources.label_google_button +import superdiary.feature.diary_auth.generated.resources.label_login +import superdiary.feature.diary_auth.generated.resources.label_login_title +import superdiary.feature.diary_auth.generated.resources.label_password +import superdiary.feature.diary_auth.generated.resources.label_register +import superdiary.feature.diary_auth.generated.resources.label_register_message +import superdiary.feature.diary_auth.generated.resources.label_username +import superdiary.feature.diary_auth.generated.resources.logo @Composable fun LoginScreenContent( diff --git a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/login/screen/LoginViewState.kt b/feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/login/screen/LoginViewState.kt similarity index 81% rename from shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/login/screen/LoginViewState.kt rename to feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/login/screen/LoginViewState.kt index 38ada1ae..c8433d63 100644 --- a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/login/screen/LoginViewState.kt +++ b/feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/login/screen/LoginViewState.kt @@ -1,4 +1,4 @@ -package com.foreverrafs.superdiary.ui.feature.auth.login.screen +package com.foreverrafs.superdiary.auth.login.screen import com.foreverrafs.auth.model.UserInfo diff --git a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/register/DeeplinkContainer.kt b/feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/register/DeeplinkContainer.kt similarity index 93% rename from shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/register/DeeplinkContainer.kt rename to feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/register/DeeplinkContainer.kt index 4fd77ee1..ee686c70 100644 --- a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/register/DeeplinkContainer.kt +++ b/feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/register/DeeplinkContainer.kt @@ -1,4 +1,4 @@ -package com.foreverrafs.superdiary.ui.feature.auth.register +package com.foreverrafs.superdiary.auth.register import androidx.core.uri.Uri @@ -39,7 +39,6 @@ class DeeplinkContainer { } val linkType = extractTypeParameter(deepLink) - val payload = deepLink require(linkType != null) { "Unable to extract link type from deeplink $deepLink" @@ -47,7 +46,7 @@ class DeeplinkContainer { pendingLink = Deeplink( type = linkType, - payload = payload, + payload = deepLink, isValid = true, ) } diff --git a/feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/register/RegisterScreenViewModel.kt b/feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/register/RegisterScreenViewModel.kt new file mode 100644 index 00000000..e9f05ea8 --- /dev/null +++ b/feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/register/RegisterScreenViewModel.kt @@ -0,0 +1,49 @@ +package com.foreverrafs.superdiary.auth.register + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.foreverrafs.auth.AuthApi +import com.foreverrafs.superdiary.auth.register.screen.RegisterScreenState +import com.foreverrafs.superdiary.common.utils.AppCoroutineDispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch + +class RegisterScreenViewModel( + private val authApi: AuthApi, + private val coroutineDispatchers: AppCoroutineDispatchers, +) : ViewModel() { + private val _viewState: MutableStateFlow = + MutableStateFlow(RegisterScreenState.Idle) + + val viewState = _viewState.asStateFlow() + + fun onRegisterClick( + name: String, + email: String, + password: String, + ) = viewModelScope.launch(coroutineDispatchers.main) { + _viewState.update { + RegisterScreenState.Processing + } + + when ( + val result = authApi.register( + name = name, + email = email, + password = password, + ) + ) { + is AuthApi.RegistrationStatus.Error -> _viewState.update { + RegisterScreenState.Error( + error = result.exception, + ) + } + + is AuthApi.RegistrationStatus.Success -> _viewState.update { + RegisterScreenState.Success + } + } + } +} diff --git a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/register/screen/RegisterScreen.kt b/feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/register/screen/RegisterScreen.kt similarity index 77% rename from shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/register/screen/RegisterScreen.kt rename to feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/register/screen/RegisterScreen.kt index 37315116..171671a2 100644 --- a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/register/screen/RegisterScreen.kt +++ b/feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/register/screen/RegisterScreen.kt @@ -1,14 +1,14 @@ -package com.foreverrafs.superdiary.ui.feature.auth.register.screen +package com.foreverrafs.superdiary.auth.register.screen import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.foreverrafs.superdiary.ui.BackHandler -import com.foreverrafs.superdiary.ui.feature.auth.register.RegisterScreenViewModel +import com.foreverrafs.superdiary.auth.register.RegisterScreenViewModel +import com.foreverrafs.superdiary.design.components.BackHandler import org.koin.compose.viewmodel.koinViewModel @Composable -fun RegisterScreenContent( +fun RegisterScreen( onLoginClick: () -> Unit, onRegisterSuccess: () -> Unit, ) { diff --git a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/register/screen/RegisterScreenContent.kt b/feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/register/screen/RegisterScreenContent.kt similarity index 95% rename from shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/register/screen/RegisterScreenContent.kt rename to feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/register/screen/RegisterScreenContent.kt index f8e3dcf6..e17bda69 100644 --- a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/register/screen/RegisterScreenContent.kt +++ b/feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/register/screen/RegisterScreenContent.kt @@ -1,4 +1,4 @@ -package com.foreverrafs.superdiary.ui.feature.auth.register.screen +package com.foreverrafs.superdiary.auth.register.screen import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Column @@ -44,14 +44,14 @@ import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.stringResource -import superdiary.shared_ui.generated.resources.Res -import superdiary.shared_ui.generated.resources.label_password -import superdiary.shared_ui.generated.resources.label_register -import superdiary.shared_ui.generated.resources.label_register_title -import superdiary.shared_ui.generated.resources.logo +import superdiary.feature.diary_auth.generated.resources.Res +import superdiary.feature.diary_auth.generated.resources.label_password +import superdiary.feature.diary_auth.generated.resources.label_register +import superdiary.feature.diary_auth.generated.resources.label_register_title +import superdiary.feature.diary_auth.generated.resources.logo @Composable -fun RegisterScreenContent( +internal fun RegisterScreenContent( viewState: RegisterScreenState, onRegisterClick: (name: String, username: String, password: String) -> Unit, onRegisterSuccess: () -> Unit, diff --git a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/register/screen/RegisterScreenState.kt b/feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/register/screen/RegisterScreenState.kt similarity index 78% rename from shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/register/screen/RegisterScreenState.kt rename to feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/register/screen/RegisterScreenState.kt index 9bef4e23..5a174f17 100644 --- a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/register/screen/RegisterScreenState.kt +++ b/feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/register/screen/RegisterScreenState.kt @@ -1,4 +1,4 @@ -package com.foreverrafs.superdiary.ui.feature.auth.register.screen +package com.foreverrafs.superdiary.auth.register.screen sealed interface RegisterScreenState { data object Success : RegisterScreenState diff --git a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/register/screen/RegistrationConfirmationScreen.kt b/feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/register/screen/RegistrationConfirmationScreen.kt similarity index 97% rename from shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/register/screen/RegistrationConfirmationScreen.kt rename to feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/register/screen/RegistrationConfirmationScreen.kt index 943b8a95..1a14355f 100644 --- a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/register/screen/RegistrationConfirmationScreen.kt +++ b/feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/register/screen/RegistrationConfirmationScreen.kt @@ -1,4 +1,4 @@ -package com.foreverrafs.superdiary.ui.feature.auth.register.screen +package com.foreverrafs.superdiary.auth.register.screen import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column diff --git a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/reset/PasswordResetViewModel.kt b/feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/reset/PasswordResetViewModel.kt similarity index 97% rename from shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/reset/PasswordResetViewModel.kt rename to feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/reset/PasswordResetViewModel.kt index 4c9722da..bf31c5c9 100644 --- a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/reset/PasswordResetViewModel.kt +++ b/feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/reset/PasswordResetViewModel.kt @@ -1,4 +1,4 @@ -package com.foreverrafs.superdiary.ui.feature.auth.reset +package com.foreverrafs.superdiary.auth.reset import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope diff --git a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/reset/SendPasswordResetScreen.kt b/feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/reset/SendPasswordResetScreen.kt similarity index 92% rename from shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/reset/SendPasswordResetScreen.kt rename to feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/reset/SendPasswordResetScreen.kt index 00705d0e..88ea08d5 100644 --- a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/reset/SendPasswordResetScreen.kt +++ b/feature/diary-auth/src/commonMain/kotlin/com/foreverrafs/superdiary/auth/reset/SendPasswordResetScreen.kt @@ -1,4 +1,4 @@ -package com.foreverrafs.superdiary.ui.feature.auth.reset +package com.foreverrafs.superdiary.auth.reset import androidx.compose.animation.Crossfade import androidx.compose.animation.core.tween @@ -37,11 +37,12 @@ import com.foreverrafs.superdiary.design.components.SuperDiaryInputField import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.stringResource import org.koin.compose.viewmodel.koinViewModel -import superdiary.shared_ui.generated.resources.Res -import superdiary.shared_ui.generated.resources.label_open_email -import superdiary.shared_ui.generated.resources.label_reset_password_button -import superdiary.shared_ui.generated.resources.label_reset_password_header -import superdiary.shared_ui.generated.resources.logo +import superdiary.feature.diary_auth.generated.resources.Res +import superdiary.feature.diary_auth.generated.resources.label_login_input_username +import superdiary.feature.diary_auth.generated.resources.label_open_email +import superdiary.feature.diary_auth.generated.resources.label_reset_password_button +import superdiary.feature.diary_auth.generated.resources.label_reset_password_header +import superdiary.feature.diary_auth.generated.resources.logo @Composable fun SendPasswordResetEmailScreen( @@ -203,7 +204,7 @@ private fun InputScreen( Column { SuperDiaryInputField( modifier = Modifier.fillMaxWidth().testTag("input_email"), - label = "Email", + label = stringResource(Res.string.label_login_input_username), value = viewState.email, onValueChange = onEmailChange, placeholder = "john.doe@gmail.com", diff --git a/shared-ui/src/commonTest/kotlin/com/foreverrafs/superdiary/ui/auth/DeeplinkContainerTest.kt b/feature/diary-auth/src/commonTest/kotlin/com/foreverrafs/superdiary/auth/DeeplinkContainerTest.kt similarity index 97% rename from shared-ui/src/commonTest/kotlin/com/foreverrafs/superdiary/ui/auth/DeeplinkContainerTest.kt rename to feature/diary-auth/src/commonTest/kotlin/com/foreverrafs/superdiary/auth/DeeplinkContainerTest.kt index 52c1d074..47b89a29 100644 --- a/shared-ui/src/commonTest/kotlin/com/foreverrafs/superdiary/ui/auth/DeeplinkContainerTest.kt +++ b/feature/diary-auth/src/commonTest/kotlin/com/foreverrafs/superdiary/auth/DeeplinkContainerTest.kt @@ -1,4 +1,4 @@ -package com.foreverrafs.superdiary.ui.auth +package com.foreverrafs.superdiary.auth import androidx.core.uri.UriUtils import assertk.assertFailure @@ -7,7 +7,7 @@ import assertk.assertions.hasClass import assertk.assertions.isEqualTo import assertk.assertions.isNotNull import assertk.assertions.isNull -import com.foreverrafs.superdiary.ui.feature.auth.register.DeeplinkContainer +import com.foreverrafs.superdiary.auth.register.DeeplinkContainer import kotlin.test.BeforeTest import kotlin.test.Test import kotlinx.coroutines.test.runTest diff --git a/shared-ui/src/commonTest/kotlin/com/foreverrafs/superdiary/ui/auth/login/BiometricLoginScreenViewModelTest.kt b/feature/diary-auth/src/commonTest/kotlin/com/foreverrafs/superdiary/auth/login/BiometricLoginScreenViewModelTest.kt similarity index 61% rename from shared-ui/src/commonTest/kotlin/com/foreverrafs/superdiary/ui/auth/login/BiometricLoginScreenViewModelTest.kt rename to feature/diary-auth/src/commonTest/kotlin/com/foreverrafs/superdiary/auth/login/BiometricLoginScreenViewModelTest.kt index 65503ed2..b56cd1b6 100644 --- a/shared-ui/src/commonTest/kotlin/com/foreverrafs/superdiary/ui/auth/login/BiometricLoginScreenViewModelTest.kt +++ b/feature/diary-auth/src/commonTest/kotlin/com/foreverrafs/superdiary/auth/login/BiometricLoginScreenViewModelTest.kt @@ -1,20 +1,16 @@ -package com.foreverrafs.superdiary.ui.auth.login +package com.foreverrafs.superdiary.auth.login import app.cash.turbine.test import assertk.assertThat +import assertk.assertions.isInstanceOf import com.foreverrafs.auth.BiometricAuth import com.foreverrafs.superdiary.common.coroutines.TestAppDispatchers import com.foreverrafs.superdiary.core.logging.AggregateLogger -import com.foreverrafs.superdiary.ui.feature.auth.login.BiometricAuthUnavailableException -import com.foreverrafs.superdiary.ui.feature.auth.login.BiometricAuthenticationException -import com.foreverrafs.superdiary.ui.feature.auth.login.BiometricLoginScreenState -import com.foreverrafs.superdiary.ui.feature.auth.login.BiometricLoginScreenViewModel import dev.mokkery.answering.returns import dev.mokkery.every import dev.mokkery.everySuspend import dev.mokkery.mock import dev.mokkery.verifySuspend -import io.ktor.util.reflect.instanceOf import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test @@ -68,41 +64,39 @@ class BiometricLoginScreenViewModelTest { advanceUntilIdle() val state = expectMostRecentItem() - assertThat(state).instanceOf(BiometricLoginScreenState.Error::class) - assertThat((state as? BiometricLoginScreenState.Error)?.exception).instanceOf( + assertThat(state).isInstanceOf(BiometricLoginScreenState.Error::class) + assertThat((state as? BiometricLoginScreenState.Error)?.exception!!).isInstanceOf( BiometricAuthUnavailableException::class, ) } } @Test - fun `Should show error screen if biometric authentication fails`() = - runTest { - every { biometricAuth.canAuthenticate() } returns true - everySuspend { biometricAuth.startBiometricAuth() } returns BiometricAuth.AuthResult.Failed + fun `Should show error screen if biometric authentication fails`() = runTest { + every { biometricAuth.canAuthenticate() } returns true + everySuspend { biometricAuth.startBiometricAuth() } returns BiometricAuth.AuthResult.Failed - loginViewModel.viewState.test { - advanceUntilIdle() - val state = expectMostRecentItem() + loginViewModel.viewState.test { + advanceUntilIdle() + val state = expectMostRecentItem() - assertThat(state).instanceOf(BiometricLoginScreenState.Error::class) - assertThat((state as? BiometricLoginScreenState.Error)?.exception).instanceOf( - BiometricAuthenticationException::class, - ) - } + assertThat(state).isInstanceOf(BiometricLoginScreenState.Error::class) + assertThat((state as? BiometricLoginScreenState.Error)?.exception!!).isInstanceOf( + BiometricAuthenticationException::class, + ) } + } @Test - fun `Should return success state when biometric authentication succeeds`() = - runTest { - every { biometricAuth.canAuthenticate() } returns true - everySuspend { biometricAuth.startBiometricAuth() } returns BiometricAuth.AuthResult.Success + fun `Should return success state when biometric authentication succeeds`() = runTest { + every { biometricAuth.canAuthenticate() } returns true + everySuspend { biometricAuth.startBiometricAuth() } returns BiometricAuth.AuthResult.Success - loginViewModel.viewState.test { - advanceUntilIdle() - val state = expectMostRecentItem() + loginViewModel.viewState.test { + advanceUntilIdle() + val state = expectMostRecentItem() - assertThat(state).instanceOf(BiometricLoginScreenState.Success::class) - } + assertThat(state).isInstanceOf(BiometricLoginScreenState.Success::class) } + } } diff --git a/shared-ui/src/commonTest/kotlin/com/foreverrafs/superdiary/ui/auth/login/FakeAuthApi.kt b/feature/diary-auth/src/commonTest/kotlin/com/foreverrafs/superdiary/auth/login/FakeAuthApi.kt similarity index 97% rename from shared-ui/src/commonTest/kotlin/com/foreverrafs/superdiary/ui/auth/login/FakeAuthApi.kt rename to feature/diary-auth/src/commonTest/kotlin/com/foreverrafs/superdiary/auth/login/FakeAuthApi.kt index 6ff5f10c..c3d03954 100644 --- a/shared-ui/src/commonTest/kotlin/com/foreverrafs/superdiary/ui/auth/login/FakeAuthApi.kt +++ b/feature/diary-auth/src/commonTest/kotlin/com/foreverrafs/superdiary/auth/login/FakeAuthApi.kt @@ -1,4 +1,4 @@ -package com.foreverrafs.superdiary.ui.auth.login +package com.foreverrafs.superdiary.auth.login import androidx.core.uri.Uri import com.foreverrafs.auth.AuthApi diff --git a/shared-ui/src/commonTest/kotlin/com/foreverrafs/superdiary/ui/auth/login/LoginScreenViewModelTest.kt b/feature/diary-auth/src/commonTest/kotlin/com/foreverrafs/superdiary/auth/login/LoginScreenViewModelTest.kt similarity index 93% rename from shared-ui/src/commonTest/kotlin/com/foreverrafs/superdiary/ui/auth/login/LoginScreenViewModelTest.kt rename to feature/diary-auth/src/commonTest/kotlin/com/foreverrafs/superdiary/auth/login/LoginScreenViewModelTest.kt index b61d490c..956fc465 100644 --- a/shared-ui/src/commonTest/kotlin/com/foreverrafs/superdiary/ui/auth/login/LoginScreenViewModelTest.kt +++ b/feature/diary-auth/src/commonTest/kotlin/com/foreverrafs/superdiary/auth/login/LoginScreenViewModelTest.kt @@ -1,4 +1,4 @@ -package com.foreverrafs.superdiary.ui.auth.login +package com.foreverrafs.superdiary.auth.login import app.cash.turbine.test import assertk.assertThat @@ -6,10 +6,9 @@ import assertk.assertions.isInstanceOf import com.foreverrafs.auth.AuthApi import com.foreverrafs.auth.model.SessionInfo import com.foreverrafs.auth.model.UserInfo +import com.foreverrafs.superdiary.auth.login.screen.LoginViewState import com.foreverrafs.superdiary.common.coroutines.TestAppDispatchers -import com.foreverrafs.superdiary.ui.awaitUntil -import com.foreverrafs.superdiary.ui.feature.auth.login.LoginScreenViewModel -import com.foreverrafs.superdiary.ui.feature.auth.login.screen.LoginViewState +import com.foreverrafs.superdiary.common.coroutines.awaitUntil import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test diff --git a/shared-ui/src/commonTest/kotlin/com/foreverrafs/superdiary/ui/auth/register/RegisterScreenViewModelTest.kt b/feature/diary-auth/src/commonTest/kotlin/com/foreverrafs/superdiary/auth/register/RegisterScreenViewModelTest.kt similarity index 79% rename from shared-ui/src/commonTest/kotlin/com/foreverrafs/superdiary/ui/auth/register/RegisterScreenViewModelTest.kt rename to feature/diary-auth/src/commonTest/kotlin/com/foreverrafs/superdiary/auth/register/RegisterScreenViewModelTest.kt index 95d0fdbc..59f3154c 100644 --- a/shared-ui/src/commonTest/kotlin/com/foreverrafs/superdiary/ui/auth/register/RegisterScreenViewModelTest.kt +++ b/feature/diary-auth/src/commonTest/kotlin/com/foreverrafs/superdiary/auth/register/RegisterScreenViewModelTest.kt @@ -1,14 +1,13 @@ -package com.foreverrafs.superdiary.ui.auth.register +package com.foreverrafs.superdiary.auth.register import app.cash.turbine.test import assertk.assertThat +import assertk.assertions.isInstanceOf import com.foreverrafs.auth.AuthApi +import com.foreverrafs.superdiary.auth.login.FakeAuthApi +import com.foreverrafs.superdiary.auth.register.screen.RegisterScreenState import com.foreverrafs.superdiary.common.coroutines.TestAppDispatchers import com.foreverrafs.superdiary.common.utils.AppCoroutineDispatchers -import com.foreverrafs.superdiary.ui.auth.login.FakeAuthApi -import com.foreverrafs.superdiary.ui.feature.auth.register.RegisterScreenViewModel -import com.foreverrafs.superdiary.ui.feature.auth.register.screen.RegisterScreenState -import io.ktor.util.reflect.instanceOf import kotlin.test.BeforeTest import kotlin.test.Test import kotlinx.coroutines.Dispatchers @@ -33,7 +32,7 @@ class RegisterScreenViewModelTest { viewModel.viewState.test { val state = awaitItem() - assertThat(state).instanceOf(RegisterScreenState.Idle::class) + assertThat(state).isInstanceOf(RegisterScreenState.Idle::class) expectNoEvents() } @@ -54,7 +53,7 @@ class RegisterScreenViewModelTest { ) // skip processing state awaitItem() - assertThat(awaitItem()).instanceOf(RegisterScreenState.Success::class) + assertThat(awaitItem()).isInstanceOf(RegisterScreenState.Success::class) expectNoEvents() } @@ -77,7 +76,7 @@ class RegisterScreenViewModelTest { // skip processing state skipItems(1) - assertThat(awaitItem()).instanceOf(RegisterScreenState.Error::class) + assertThat(awaitItem()).isInstanceOf(RegisterScreenState.Error::class) expectNoEvents() } diff --git a/shared-ui/src/commonTest/kotlin/com/foreverrafs/superdiary/ui/auth/reset/PasswordResetViewModelTest.kt b/feature/diary-auth/src/commonTest/kotlin/com/foreverrafs/superdiary/auth/reset/PasswordResetViewModelTest.kt similarity index 95% rename from shared-ui/src/commonTest/kotlin/com/foreverrafs/superdiary/ui/auth/reset/PasswordResetViewModelTest.kt rename to feature/diary-auth/src/commonTest/kotlin/com/foreverrafs/superdiary/auth/reset/PasswordResetViewModelTest.kt index b440e1ca..7f0e67f7 100644 --- a/shared-ui/src/commonTest/kotlin/com/foreverrafs/superdiary/ui/auth/reset/PasswordResetViewModelTest.kt +++ b/feature/diary-auth/src/commonTest/kotlin/com/foreverrafs/superdiary/auth/reset/PasswordResetViewModelTest.kt @@ -1,4 +1,4 @@ -package com.foreverrafs.superdiary.ui.auth.reset +package com.foreverrafs.superdiary.auth.reset import app.cash.turbine.test import assertk.assertThat @@ -8,10 +8,10 @@ import assertk.assertions.isFalse import assertk.assertions.isNotNull import assertk.assertions.isNull import assertk.assertions.isTrue +import com.foreverrafs.superdiary.auth.login.FakeAuthApi +import com.foreverrafs.superdiary.auth.reset.PasswordResetViewModel import com.foreverrafs.superdiary.common.coroutines.TestAppDispatchers import com.foreverrafs.superdiary.common.utils.AppCoroutineDispatchers -import com.foreverrafs.superdiary.ui.auth.login.FakeAuthApi -import com.foreverrafs.superdiary.ui.feature.auth.reset.PasswordResetViewModel import kotlin.test.BeforeTest import kotlin.test.Test import kotlinx.coroutines.Dispatchers diff --git a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_LoginScreenSnapshotTest_Login_Screen_-_Idle[PIXEL_6_DARK].png b/feature/diary-auth/src/test/snapshots/images/com.foreverrafs.superdiary.auth_LoginScreenSnapshotTest_Login_Screen_-_Idle[PIXEL_6_DARK].png similarity index 100% rename from shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_LoginScreenSnapshotTest_Login_Screen_-_Idle[PIXEL_6_DARK].png rename to feature/diary-auth/src/test/snapshots/images/com.foreverrafs.superdiary.auth_LoginScreenSnapshotTest_Login_Screen_-_Idle[PIXEL_6_DARK].png diff --git a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_LoginScreenSnapshotTest_Login_Screen_-_Idle[PIXEL_6_LIGHT].png b/feature/diary-auth/src/test/snapshots/images/com.foreverrafs.superdiary.auth_LoginScreenSnapshotTest_Login_Screen_-_Idle[PIXEL_6_LIGHT].png similarity index 100% rename from shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_LoginScreenSnapshotTest_Login_Screen_-_Idle[PIXEL_6_LIGHT].png rename to feature/diary-auth/src/test/snapshots/images/com.foreverrafs.superdiary.auth_LoginScreenSnapshotTest_Login_Screen_-_Idle[PIXEL_6_LIGHT].png diff --git a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_LoginScreenSnapshotTest_Login_Screen_-_Processing[PIXEL_6_DARK].png b/feature/diary-auth/src/test/snapshots/images/com.foreverrafs.superdiary.auth_LoginScreenSnapshotTest_Login_Screen_-_Processing[PIXEL_6_DARK].png similarity index 100% rename from shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_LoginScreenSnapshotTest_Login_Screen_-_Processing[PIXEL_6_DARK].png rename to feature/diary-auth/src/test/snapshots/images/com.foreverrafs.superdiary.auth_LoginScreenSnapshotTest_Login_Screen_-_Processing[PIXEL_6_DARK].png diff --git a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_LoginScreenSnapshotTest_Login_Screen_-_Processing[PIXEL_6_LIGHT].png b/feature/diary-auth/src/test/snapshots/images/com.foreverrafs.superdiary.auth_LoginScreenSnapshotTest_Login_Screen_-_Processing[PIXEL_6_LIGHT].png similarity index 100% rename from shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_LoginScreenSnapshotTest_Login_Screen_-_Processing[PIXEL_6_LIGHT].png rename to feature/diary-auth/src/test/snapshots/images/com.foreverrafs.superdiary.auth_LoginScreenSnapshotTest_Login_Screen_-_Processing[PIXEL_6_LIGHT].png diff --git a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Email_Sent_State[PIXEL_6_DARK].png b/feature/diary-auth/src/test/snapshots/images/com.foreverrafs.superdiary.auth_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Email_Sent_State[PIXEL_6_DARK].png similarity index 100% rename from shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Email_Sent_State[PIXEL_6_DARK].png rename to feature/diary-auth/src/test/snapshots/images/com.foreverrafs.superdiary.auth_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Email_Sent_State[PIXEL_6_DARK].png diff --git a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Email_Sent_State[PIXEL_6_LIGHT].png b/feature/diary-auth/src/test/snapshots/images/com.foreverrafs.superdiary.auth_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Email_Sent_State[PIXEL_6_LIGHT].png similarity index 100% rename from shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Email_Sent_State[PIXEL_6_LIGHT].png rename to feature/diary-auth/src/test/snapshots/images/com.foreverrafs.superdiary.auth_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Email_Sent_State[PIXEL_6_LIGHT].png diff --git a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Initial_State[PIXEL_6_DARK].png b/feature/diary-auth/src/test/snapshots/images/com.foreverrafs.superdiary.auth_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Initial_State[PIXEL_6_DARK].png similarity index 100% rename from shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Initial_State[PIXEL_6_DARK].png rename to feature/diary-auth/src/test/snapshots/images/com.foreverrafs.superdiary.auth_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Initial_State[PIXEL_6_DARK].png diff --git a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Initial_State[PIXEL_6_LIGHT].png b/feature/diary-auth/src/test/snapshots/images/com.foreverrafs.superdiary.auth_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Initial_State[PIXEL_6_LIGHT].png similarity index 100% rename from shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Initial_State[PIXEL_6_LIGHT].png rename to feature/diary-auth/src/test/snapshots/images/com.foreverrafs.superdiary.auth_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Initial_State[PIXEL_6_LIGHT].png diff --git a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Input_Invalid_State[PIXEL_6_DARK].png b/feature/diary-auth/src/test/snapshots/images/com.foreverrafs.superdiary.auth_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Input_Invalid_State[PIXEL_6_DARK].png similarity index 100% rename from shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Input_Invalid_State[PIXEL_6_DARK].png rename to feature/diary-auth/src/test/snapshots/images/com.foreverrafs.superdiary.auth_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Input_Invalid_State[PIXEL_6_DARK].png diff --git a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Input_Invalid_State[PIXEL_6_LIGHT].png b/feature/diary-auth/src/test/snapshots/images/com.foreverrafs.superdiary.auth_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Input_Invalid_State[PIXEL_6_LIGHT].png similarity index 100% rename from shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Input_Invalid_State[PIXEL_6_LIGHT].png rename to feature/diary-auth/src/test/snapshots/images/com.foreverrafs.superdiary.auth_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Input_Invalid_State[PIXEL_6_LIGHT].png diff --git a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Input_Valid_State[PIXEL_6_DARK].png b/feature/diary-auth/src/test/snapshots/images/com.foreverrafs.superdiary.auth_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Input_Valid_State[PIXEL_6_DARK].png similarity index 100% rename from shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Input_Valid_State[PIXEL_6_DARK].png rename to feature/diary-auth/src/test/snapshots/images/com.foreverrafs.superdiary.auth_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Input_Valid_State[PIXEL_6_DARK].png diff --git a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Input_Valid_State[PIXEL_6_LIGHT].png b/feature/diary-auth/src/test/snapshots/images/com.foreverrafs.superdiary.auth_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Input_Valid_State[PIXEL_6_LIGHT].png similarity index 100% rename from shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Input_Valid_State[PIXEL_6_LIGHT].png rename to feature/diary-auth/src/test/snapshots/images/com.foreverrafs.superdiary.auth_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Input_Valid_State[PIXEL_6_LIGHT].png diff --git a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Invalid_Email_State[PIXEL_6_DARK].png b/feature/diary-auth/src/test/snapshots/images/com.foreverrafs.superdiary.auth_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Invalid_Email_State[PIXEL_6_DARK].png similarity index 100% rename from shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Invalid_Email_State[PIXEL_6_DARK].png rename to feature/diary-auth/src/test/snapshots/images/com.foreverrafs.superdiary.auth_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Invalid_Email_State[PIXEL_6_DARK].png diff --git a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Invalid_Email_State[PIXEL_6_LIGHT].png b/feature/diary-auth/src/test/snapshots/images/com.foreverrafs.superdiary.auth_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Invalid_Email_State[PIXEL_6_LIGHT].png similarity index 100% rename from shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Invalid_Email_State[PIXEL_6_LIGHT].png rename to feature/diary-auth/src/test/snapshots/images/com.foreverrafs.superdiary.auth_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Invalid_Email_State[PIXEL_6_LIGHT].png diff --git a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Loading_State[PIXEL_6_DARK].png b/feature/diary-auth/src/test/snapshots/images/com.foreverrafs.superdiary.auth_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Loading_State[PIXEL_6_DARK].png similarity index 100% rename from shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Loading_State[PIXEL_6_DARK].png rename to feature/diary-auth/src/test/snapshots/images/com.foreverrafs.superdiary.auth_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Loading_State[PIXEL_6_DARK].png diff --git a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Loading_State[PIXEL_6_LIGHT].png b/feature/diary-auth/src/test/snapshots/images/com.foreverrafs.superdiary.auth_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Loading_State[PIXEL_6_LIGHT].png similarity index 100% rename from shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Loading_State[PIXEL_6_LIGHT].png rename to feature/diary-auth/src/test/snapshots/images/com.foreverrafs.superdiary.auth_SendPasswordResetScreenSnapshotTest_Password_Reset_Screen_-_Loading_State[PIXEL_6_LIGHT].png diff --git a/feature/diary-profile/build.gradle.kts b/feature/diary-profile/build.gradle.kts index 4a9b52b1..322f599a 100644 --- a/feature/diary-profile/build.gradle.kts +++ b/feature/diary-profile/build.gradle.kts @@ -29,6 +29,7 @@ kotlin { implementation(libs.kotlinx.coroutines.core) implementation(libs.koin.core) implementation(compose.foundation) + implementation(compose.components.resources) implementation(compose.material3) implementation(compose.materialIconsExtended) implementation(projects.core.logging) @@ -36,7 +37,7 @@ kotlin { implementation(libs.koin.compose) implementation(libs.koin.compose.viewmodel) implementation(libs.kotlin.datetime) - implementation(projects.core.auth) + implementation(projects.core.authentication) implementation(projects.commonUtils) implementation(projects.sharedData) implementation("org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose:2.8.4") diff --git a/feature/diary-profile/src/androidMain/kotlin/com/foreverrafs/superdiary/profile/presentation/screen/ConfirmLogoutDialog.android.kt b/feature/diary-profile/src/androidMain/kotlin/com/foreverrafs/superdiary/profile/presentation/screen/ConfirmLogoutDialog.android.kt new file mode 100644 index 00000000..034deb6b --- /dev/null +++ b/feature/diary-profile/src/androidMain/kotlin/com/foreverrafs/superdiary/profile/presentation/screen/ConfirmLogoutDialog.android.kt @@ -0,0 +1,32 @@ +package com.foreverrafs.superdiary.profile.presentation.screen + +import androidx.compose.runtime.Composable +import androidx.compose.ui.window.DialogProperties +import com.foreverrafs.superdiary.design.components.BasicMaterialDialog +import org.jetbrains.compose.resources.stringResource +import superdiary.feature.diary_profile.generated.resources.Res +import superdiary.feature.diary_profile.generated.resources.confirm_logout_dialog_cancel_button +import superdiary.feature.diary_profile.generated.resources.confirm_logout_dialog_confirm_button +import superdiary.feature.diary_profile.generated.resources.confirm_logout_dialog_message +import superdiary.feature.diary_profile.generated.resources.confirm_logout_dialog_title + +@Composable +actual fun ConfirmLogoutDialog( + onLogout: () -> Unit, + onDismiss: () -> Unit, + onDismissRequest: () -> Unit, +) { + BasicMaterialDialog( + onNegativeButton = onLogout, + onPositiveButton = onDismiss, + title = stringResource(Res.string.confirm_logout_dialog_title), + message = stringResource(Res.string.confirm_logout_dialog_message), + negativeButtonText = stringResource(Res.string.confirm_logout_dialog_confirm_button), + positiveButtonText = stringResource(Res.string.confirm_logout_dialog_cancel_button), + onDismissRequest = onDismissRequest, + properties = DialogProperties( + dismissOnBackPress = false, + dismissOnClickOutside = false, + ), + ) +} diff --git a/feature/diary-profile/src/commonMain/composeResources/values/strings.xml b/feature/diary-profile/src/commonMain/composeResources/values/strings.xml new file mode 100644 index 00000000..2b66a42c --- /dev/null +++ b/feature/diary-profile/src/commonMain/composeResources/values/strings.xml @@ -0,0 +1,6 @@ + + Confirm logout + Are you sure you want to logout? + Cancel + Logout + diff --git a/feature/diary-profile/src/commonMain/kotlin/com/foreverrafs/superdiary/profile/presentation/ProfileScreenViewModel.kt b/feature/diary-profile/src/commonMain/kotlin/com/foreverrafs/superdiary/profile/presentation/ProfileScreenViewModel.kt index 9a96b6d2..1512b1e7 100644 --- a/feature/diary-profile/src/commonMain/kotlin/com/foreverrafs/superdiary/profile/presentation/ProfileScreenViewModel.kt +++ b/feature/diary-profile/src/commonMain/kotlin/com/foreverrafs/superdiary/profile/presentation/ProfileScreenViewModel.kt @@ -3,7 +3,7 @@ package com.foreverrafs.superdiary.profile.presentation import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.foreverrafs.auth.AuthApi -import com.foreverrafs.superdiary.utils.DiaryPreference +import com.foreverrafs.preferences.DiaryPreference import com.foreverrafs.superdiary.utils.DiarySettings import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted diff --git a/feature/diary-profile/src/commonMain/kotlin/com/foreverrafs/superdiary/profile/presentation/screen/ConfirmLogoutDialog.kt b/feature/diary-profile/src/commonMain/kotlin/com/foreverrafs/superdiary/profile/presentation/screen/ConfirmLogoutDialog.kt new file mode 100644 index 00000000..e0ef2762 --- /dev/null +++ b/feature/diary-profile/src/commonMain/kotlin/com/foreverrafs/superdiary/profile/presentation/screen/ConfirmLogoutDialog.kt @@ -0,0 +1,10 @@ +package com.foreverrafs.superdiary.profile.presentation.screen + +import androidx.compose.runtime.Composable + +@Composable +expect fun ConfirmLogoutDialog( + onLogout: () -> Unit, + onDismiss: () -> Unit, + onDismissRequest: () -> Unit, +) diff --git a/feature/diary-profile/src/commonMain/kotlin/com/foreverrafs/superdiary/profile/presentation/screen/ProfileScreen.kt b/feature/diary-profile/src/commonMain/kotlin/com/foreverrafs/superdiary/profile/presentation/screen/ProfileScreen.kt index 7821c34b..7ac84cc2 100644 --- a/feature/diary-profile/src/commonMain/kotlin/com/foreverrafs/superdiary/profile/presentation/screen/ProfileScreen.kt +++ b/feature/diary-profile/src/commonMain/kotlin/com/foreverrafs/superdiary/profile/presentation/screen/ProfileScreen.kt @@ -47,7 +47,6 @@ import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.foreverrafs.superdiary.design.components.ConfirmLogoutDialog import com.foreverrafs.superdiary.design.components.SuperDiaryImage import com.foreverrafs.superdiary.design.style.SuperDiaryTheme import com.foreverrafs.superdiary.profile.presentation.ProfileScreenViewData diff --git a/feature/diary-profile/src/commonTest/kotlin/com/foreverrafs/superdiary/profile/ProfileScreenViewModelTest.kt b/feature/diary-profile/src/commonTest/kotlin/com/foreverrafs/superdiary/profile/ProfileScreenViewModelTest.kt index 2bb4d518..07eb7f3a 100644 --- a/feature/diary-profile/src/commonTest/kotlin/com/foreverrafs/superdiary/profile/ProfileScreenViewModelTest.kt +++ b/feature/diary-profile/src/commonTest/kotlin/com/foreverrafs/superdiary/profile/ProfileScreenViewModelTest.kt @@ -7,8 +7,8 @@ import assertk.assertions.isNotNull import assertk.assertions.isNull import com.foreverrafs.auth.AuthApi import com.foreverrafs.auth.model.UserInfo +import com.foreverrafs.preferences.DiaryPreference import com.foreverrafs.superdiary.profile.presentation.ProfileScreenViewModel -import com.foreverrafs.superdiary.utils.DiaryPreference import com.foreverrafs.superdiary.utils.DiarySettings import dev.mokkery.answering.returns import dev.mokkery.every @@ -29,7 +29,6 @@ import kotlinx.coroutines.test.setMain class ProfileScreenViewModelTest { private lateinit var profileScreenViewModel: ProfileScreenViewModel - // TODO: Replace this mock with a fake private val authApi: AuthApi = mock() private val preference: DiaryPreference = mock() diff --git a/feature/diary-profile/src/iosMain/kotlin/com/foreverrafs/superdiary/profile/presentation/screen/ConfirmLogoutDialog.ios.kt b/feature/diary-profile/src/iosMain/kotlin/com/foreverrafs/superdiary/profile/presentation/screen/ConfirmLogoutDialog.ios.kt new file mode 100644 index 00000000..6e324f0a --- /dev/null +++ b/feature/diary-profile/src/iosMain/kotlin/com/foreverrafs/superdiary/profile/presentation/screen/ConfirmLogoutDialog.ios.kt @@ -0,0 +1,27 @@ +package com.foreverrafs.superdiary.profile.presentation.screen + +import androidx.compose.runtime.Composable +import com.foreverrafs.superdiary.design.components.BasicCupertinoDialog +import org.jetbrains.compose.resources.stringResource +import superdiary.feature.diary_profile.generated.resources.Res +import superdiary.feature.diary_profile.generated.resources.confirm_logout_dialog_cancel_button +import superdiary.feature.diary_profile.generated.resources.confirm_logout_dialog_confirm_button +import superdiary.feature.diary_profile.generated.resources.confirm_logout_dialog_message +import superdiary.feature.diary_profile.generated.resources.confirm_logout_dialog_title + +@Composable +actual fun ConfirmLogoutDialog( + onLogout: () -> Unit, + onDismiss: () -> Unit, + onDismissRequest: () -> Unit, +) { + BasicCupertinoDialog( + onNegativeButton = onLogout, + onPositiveButton = onDismiss, + title = stringResource(Res.string.confirm_logout_dialog_title), + message = stringResource(Res.string.confirm_logout_dialog_message), + negativeButtonText = stringResource(Res.string.confirm_logout_dialog_confirm_button), + positiveButtonText = stringResource(Res.string.confirm_logout_dialog_cancel_button), + onDismissRequest = onDismissRequest, + ) +} diff --git a/feature/diary-profile/src/jvmMain/kotlin/com/foreverrafs/superdiary/profile/presentation/screen/ConfirmLogoutDialog.jvm.kt b/feature/diary-profile/src/jvmMain/kotlin/com/foreverrafs/superdiary/profile/presentation/screen/ConfirmLogoutDialog.jvm.kt new file mode 100644 index 00000000..034deb6b --- /dev/null +++ b/feature/diary-profile/src/jvmMain/kotlin/com/foreverrafs/superdiary/profile/presentation/screen/ConfirmLogoutDialog.jvm.kt @@ -0,0 +1,32 @@ +package com.foreverrafs.superdiary.profile.presentation.screen + +import androidx.compose.runtime.Composable +import androidx.compose.ui.window.DialogProperties +import com.foreverrafs.superdiary.design.components.BasicMaterialDialog +import org.jetbrains.compose.resources.stringResource +import superdiary.feature.diary_profile.generated.resources.Res +import superdiary.feature.diary_profile.generated.resources.confirm_logout_dialog_cancel_button +import superdiary.feature.diary_profile.generated.resources.confirm_logout_dialog_confirm_button +import superdiary.feature.diary_profile.generated.resources.confirm_logout_dialog_message +import superdiary.feature.diary_profile.generated.resources.confirm_logout_dialog_title + +@Composable +actual fun ConfirmLogoutDialog( + onLogout: () -> Unit, + onDismiss: () -> Unit, + onDismissRequest: () -> Unit, +) { + BasicMaterialDialog( + onNegativeButton = onLogout, + onPositiveButton = onDismiss, + title = stringResource(Res.string.confirm_logout_dialog_title), + message = stringResource(Res.string.confirm_logout_dialog_message), + negativeButtonText = stringResource(Res.string.confirm_logout_dialog_confirm_button), + positiveButtonText = stringResource(Res.string.confirm_logout_dialog_cancel_button), + onDismissRequest = onDismissRequest, + properties = DialogProperties( + dismissOnBackPress = false, + dismissOnClickOutside = false, + ), + ) +} diff --git a/iosApp/iosApp/auth/GoogleTokenProviderImpl.swift b/iosApp/iosApp/auth/GoogleTokenProviderImpl.swift index 0d6c09aa..c8d62a79 100644 --- a/iosApp/iosApp/auth/GoogleTokenProviderImpl.swift +++ b/iosApp/iosApp/auth/GoogleTokenProviderImpl.swift @@ -23,10 +23,18 @@ class GoogleTokenProviderImpl : GoogleTokenProvider{ guard let rootViewController = await keyWindow?.rootViewController else { throw GoogleTokenProviderException(message: "Could not find root view controller") } + return try await signInWithGoogle(rootViewController: rootViewController) + } + + @MainActor + func signInWithGoogle(rootViewController: UIViewController) async throws -> String { let result = try await GIDSignIn.sharedInstance.signIn(withPresenting: rootViewController) - guard let token = result.user.idToken?.tokenString else { throw GoogleTokenProviderException(message: "Error getting token from Google Sign in result") } - + + guard let token = result.user.idToken?.tokenString else { + throw GoogleTokenProviderException(message: "Error getting token from Google Sign in result") + } + return token } diff --git a/preferences/annotation/build.gradle.kts b/preferences/annotation/build.gradle.kts new file mode 100644 index 00000000..32e13c99 --- /dev/null +++ b/preferences/annotation/build.gradle.kts @@ -0,0 +1,22 @@ +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.android.library) +} + +kotlin { + jvm() + iosX64() + iosArm64() + iosSimulatorArm64() + androidTarget() +} + +android { + namespace = "com.foreverrafs.preferences" + compileSdk = 35 + + compileOptions { + targetCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.VERSION_17 + } +} diff --git a/preferences/annotation/src/commonMain/kotlin/com/foreverrafs/preferences/Preference.kt b/preferences/annotation/src/commonMain/kotlin/com/foreverrafs/preferences/Preference.kt new file mode 100644 index 00000000..79138221 --- /dev/null +++ b/preferences/annotation/src/commonMain/kotlin/com/foreverrafs/preferences/Preference.kt @@ -0,0 +1,7 @@ +package com.foreverrafs.preferences + +@Suppress("unused") +@Target(AnnotationTarget.CLASS) +annotation class Preference( + val generatedClassName: String, +) diff --git a/preferences/annotation/src/commonMain/kotlin/com/foreverrafs/preferences/PreferenceKey.kt b/preferences/annotation/src/commonMain/kotlin/com/foreverrafs/preferences/PreferenceKey.kt new file mode 100644 index 00000000..49251969 --- /dev/null +++ b/preferences/annotation/src/commonMain/kotlin/com/foreverrafs/preferences/PreferenceKey.kt @@ -0,0 +1,20 @@ +package com.foreverrafs.preferences + +@Suppress("unused") +@Target(AnnotationTarget.PROPERTY) +annotation class PreferenceKey { + @Target(AnnotationTarget.PROPERTY) + annotation class String(val default: kotlin.String) + + @Target(AnnotationTarget.PROPERTY) + annotation class Int(val default: kotlin.Int) + + @Target(AnnotationTarget.PROPERTY) + annotation class Boolean(val default: kotlin.Boolean) + + @Target(AnnotationTarget.PROPERTY) + annotation class Float(val default: kotlin.Float) + + @Target(AnnotationTarget.PROPERTY) + annotation class Double(val default: kotlin.Double) +} diff --git a/preferences/processor/build.gradle.kts b/preferences/processor/build.gradle.kts new file mode 100644 index 00000000..32b05f88 --- /dev/null +++ b/preferences/processor/build.gradle.kts @@ -0,0 +1,28 @@ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.multiplatform) +} + +kotlin { + jvm() + androidTarget() + + sourceSets { + commonMain.dependencies { + implementation(projects.preferences.annotation) + implementation(libs.square.kotlinPoet) + implementation("com.squareup:kotlinpoet-ksp:2.0.0") + implementation("com.google.devtools.ksp:symbol-processing-api:2.1.10-1.0.29") + } + } +} + +android { + namespace = "com.foreverrafs.preferences.processor" + compileSdk = libs.versions.compileSdk.get().toInt() + + compileOptions { + targetCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.VERSION_17 + } +} diff --git a/preferences/processor/src/commonMain/kotlin/com/foreverrafs/preferences/PreferenceProcessor.kt b/preferences/processor/src/commonMain/kotlin/com/foreverrafs/preferences/PreferenceProcessor.kt new file mode 100644 index 00000000..4c498c6c --- /dev/null +++ b/preferences/processor/src/commonMain/kotlin/com/foreverrafs/preferences/PreferenceProcessor.kt @@ -0,0 +1,120 @@ +@file:Suppress("UnnecessaryVariable") + +package com.foreverrafs.preferences + +import com.foreverrafs.preferences.codegen.PreferencesCodeGenerator +import com.foreverrafs.preferences.codegen.filterDataClassesWithAnnotation +import com.google.devtools.ksp.processing.CodeGenerator +import com.google.devtools.ksp.processing.Dependencies +import com.google.devtools.ksp.processing.KSPLogger +import com.google.devtools.ksp.processing.Resolver +import com.google.devtools.ksp.processing.SymbolProcessor +import com.google.devtools.ksp.processing.SymbolProcessorEnvironment +import com.google.devtools.ksp.processing.SymbolProcessorProvider +import com.google.devtools.ksp.symbol.KSAnnotated +import com.google.devtools.ksp.symbol.KSAnnotation +import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.symbol.KSVisitorVoid +import com.google.devtools.ksp.validate +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.ksp.toClassName +import com.squareup.kotlinpoet.ksp.writeTo + +class PreferenceProcessor( + private val options: Map, + private val logger: KSPLogger, + private val codeGenerator: CodeGenerator, +) : SymbolProcessor { + private val preferencesCodeGenerator = PreferencesCodeGenerator() + + override fun process(resolver: Resolver): List { + val symbols = resolver + .filterDataClassesWithAnnotation(PREFERENCE_ANNOTATION_FQN) + + if (!symbols.iterator().hasNext()) return emptyList() + + symbols.forEach { symbol -> + symbol.accept( + visitor = Visitor( + codeGenerator = codeGenerator, + resolver = resolver, + ), + data = Unit, + ) + } + + val unableToProcess = symbols.filterNot { it.validate() }.toList() + return unableToProcess + } + + inner class Visitor( + private val codeGenerator: CodeGenerator, + private val resolver: Resolver, + ) : KSVisitorVoid() { + + override fun visitClassDeclaration( + classDeclaration: KSClassDeclaration, + data: Unit, + ) { + // Getting the list of member properties of the annotated interface. + val properties = classDeclaration + .getAllProperties() + .filter { it.validate() } + + val annotation: KSAnnotation = classDeclaration.annotations.first { + it.shortName.asString() == PREFERENCE_ANNOTATION_SIMPLE_NAME + } + + val nameArgument = annotation.arguments + .first { arg -> arg.name?.asString() == "generatedClassName" } + + preferencesCodeGenerator.generatePreferenceInterface( + preferenceClass = ClassName( + packageName = ROOT_PACKAGE, + nameArgument.value as String, + ), + settingsClass = classDeclaration.toClassName(), + ).writeTo( + codeGenerator = codeGenerator, + dependencies = Dependencies( + aggregating = false, + sources = resolver.getAllFiles().toList().toTypedArray(), + ), + ) + + preferencesCodeGenerator.generatePreferenceClass( + concreteClass = ClassName( + packageName = ROOT_PACKAGE, + "${nameArgument.value as String}Impl", + ), + interfaceClass = ClassName( + packageName = ROOT_PACKAGE, + nameArgument.value as String, + ), + settingsClass = classDeclaration.toClassName(), + properties = properties.toList(), + ).writeTo( + codeGenerator = codeGenerator, + dependencies = Dependencies( + aggregating = false, + sources = resolver.getAllFiles().toList().toTypedArray(), + ), + ) + } + } + + companion object { + private const val PREFERENCE_ANNOTATION_SIMPLE_NAME = "Preference" + private const val ROOT_PACKAGE = "com.foreverrafs.preferences" + private const val PREFERENCE_ANNOTATION_FQN = "$ROOT_PACKAGE.Preference" + } +} + +class PreferenceProcessorProvider : SymbolProcessorProvider { + override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor = + PreferenceProcessor( + options = environment.options, + logger = environment.logger, + codeGenerator = environment.codeGenerator, + ) +} diff --git a/preferences/processor/src/commonMain/kotlin/com/foreverrafs/preferences/codegen/Extensions.kt b/preferences/processor/src/commonMain/kotlin/com/foreverrafs/preferences/codegen/Extensions.kt new file mode 100644 index 00000000..33a66703 --- /dev/null +++ b/preferences/processor/src/commonMain/kotlin/com/foreverrafs/preferences/codegen/Extensions.kt @@ -0,0 +1,47 @@ +package com.foreverrafs.preferences.codegen + +import com.google.devtools.ksp.processing.Resolver +import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.symbol.KSPropertyDeclaration +import com.google.devtools.ksp.symbol.Modifier +import com.squareup.kotlinpoet.asClassName +import com.squareup.kotlinpoet.ksp.toClassName + +fun Resolver.filterDataClassesWithAnnotation(annotation: String): Sequence = + getSymbolsWithAnnotation(annotation) + .filterIsInstance() + .filter { + Modifier.DATA in it.modifiers + } + +fun KSPropertyDeclaration.getValueAsString(): String { + fun illegalStateException(): Unit = + throw IllegalStateException( + "Property and annotation type mismatch for ${this.simpleName.asString()}." + + "Please Ensure that the property type is the same as the @PreferenceKey type used", + ) + + val resolvedProperty = type.resolve().toClassName() + + val firstAnnotatedValue = annotations + .firstOrNull() + ?.arguments + ?.firstOrNull() + ?.value + ?: throw IllegalStateException( + "The property ${simpleName.asString()} hasn't been annotated with @PreferenceKey." + + "Please ensure all properties have been annotated", + ) + + val value = when (resolvedProperty) { + Boolean::class.asClassName() -> firstAnnotatedValue as? Boolean ?: illegalStateException() + Int::class.asClassName() -> firstAnnotatedValue as? Int ?: illegalStateException() + Float::class.asClassName() -> firstAnnotatedValue as? Float ?: illegalStateException() + Long::class.asClassName() -> firstAnnotatedValue as? Long ?: illegalStateException() + Double::class.asClassName() -> firstAnnotatedValue as? Double ?: illegalStateException() + String::class.asClassName() -> "\"${firstAnnotatedValue as? String ?: illegalStateException()}\"" + else -> null + } + + return value.toString() +} diff --git a/preferences/processor/src/commonMain/kotlin/com/foreverrafs/preferences/codegen/PreferencesCodeGenerator.kt b/preferences/processor/src/commonMain/kotlin/com/foreverrafs/preferences/codegen/PreferencesCodeGenerator.kt new file mode 100644 index 00000000..0a236199 --- /dev/null +++ b/preferences/processor/src/commonMain/kotlin/com/foreverrafs/preferences/codegen/PreferencesCodeGenerator.kt @@ -0,0 +1,307 @@ +package com.foreverrafs.preferences.codegen + +import com.google.devtools.ksp.symbol.KSPropertyDeclaration +import com.squareup.kotlinpoet.AnnotationSpec +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.LambdaTypeName +import com.squareup.kotlinpoet.MemberName +import com.squareup.kotlinpoet.ParameterSpec +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeSpec +import com.squareup.kotlinpoet.asClassName +import com.squareup.kotlinpoet.ksp.toClassName + +class PreferencesCodeGenerator { + + /** + * Generates the Preferences class by delegating operations to individual + * private functions. + * + * @param concreteClass This is the main class that will be created. The + * name of this class is obtained as a parameter passed to the + * annotation + * @param settingsClass This is the name of the data class which has been + * annotated with [@Preference] + * @param properties These are the individual members of the data class + */ + fun generatePreferenceClass( + concreteClass: ClassName, + settingsClass: ClassName, + properties: List, + interfaceClass: ClassName, + ): FileSpec { + val preferenceKeyProperties = createPreferenceKeyProperties(properties) + + val preferenceClassBuilder = createPreferenceClassBuilder( + preferenceClass = concreteClass, + interfaceClass = interfaceClass, + settingsClass = settingsClass, + preferenceKeyProperties = preferenceKeyProperties, + properties = properties, + ) + + return FileSpec.builder( + packageName = concreteClass.packageName, + fileName = concreteClass.simpleName, + ).addType(preferenceClassBuilder).build() + } + + fun generatePreferenceInterface( + preferenceClass: ClassName, + settingsClass: ClassName, + ): FileSpec { + val settingsSpec = PropertySpec.builder( + name = "settings", + type = ClassName("kotlinx.coroutines.flow", "Flow").parameterizedBy( + settingsClass, + ), + ).build() + + val saveFunSpec = + FunSpec.builder("save").addModifiers(KModifier.SUSPEND, KModifier.ABSTRACT) + .addParameter( + "block", + LambdaTypeName.get( + returnType = settingsClass, + parameters = listOf( + ParameterSpec.builder("", settingsClass).build(), + ), + ), + ).build() + + val snapshotFunSpec = FunSpec.builder("getSnapshot") + .addModifiers(KModifier.SUSPEND, KModifier.ABSTRACT) + .returns(settingsClass) + .build() + + val clearFunSpec = FunSpec.builder("clear") + .addModifiers(KModifier.ABSTRACT, KModifier.SUSPEND) + .build() + + val preferenceInterfaceSpec = TypeSpec.interfaceBuilder( + preferenceClass, + ).addProperty(settingsSpec) + .addFunction(clearFunSpec) + .addFunction(snapshotFunSpec) + .addFunction(saveFunSpec).build() + + return FileSpec.builder( + packageName = preferenceClass.packageName, + fileName = preferenceClass.simpleName, + ).addType(preferenceInterfaceSpec).build() + } + + private fun createPreferenceClassBuilder( + preferenceClass: ClassName, + settingsClass: ClassName, + preferenceKeyProperties: List, + properties: List, + interfaceClass: ClassName, + ): TypeSpec { + val dataStore = ClassName("androidx.datastore.core", "DataStore") + val preferences = ClassName("androidx.datastore.preferences.core", "Preferences") + + val constructor = createConstructor() + + val settingsProperty = createSettingsPropertySpec(settingsClass, properties) + val getSnapshotFun = createGetSnapshotFunctionSpec(settingsClass, properties) + val saveFun = createSaveFunctionSpec(settingsClass, properties) + val clearFun = createClearFunctionSpec() + val companionObject = createCompanionObjectSpec(preferenceClass) + + return TypeSpec.classBuilder(preferenceClass) + .addSuperinterfaces( + listOf(interfaceClass), + ) + .primaryConstructor(constructor).addProperty( + PropertySpec.builder("dataStore", dataStore.parameterizedBy(preferences)) + .initializer("dataStore").addModifiers(KModifier.PRIVATE).build(), + ) + .addProperties(preferenceKeyProperties) + .addProperty(settingsProperty) + .addFunction(getSnapshotFun) + .addFunction(saveFun) + .addFunction(clearFun) + .addType(companionObject) + .build() + } + + private fun createConstructor(): FunSpec = FunSpec.constructorBuilder() + .addParameter( + ParameterSpec.builder( + "dataStore", + ClassName("androidx.datastore.core", "DataStore").parameterizedBy( + ClassName("androidx.datastore.preferences.core", "Preferences"), + ), + ).build(), + ).build() + + /** + * Creates a settings property which emits changes to the preferences. The + * generated property is a pure Kotlin data class and can be observed as is + */ + private fun createSettingsPropertySpec( + settingsClass: ClassName, + properties: List, + ): PropertySpec { + val flow = ClassName("kotlinx.coroutines.flow", "Flow") + val map = MemberName("kotlinx.coroutines.flow", "map") + + return PropertySpec.builder("settings", flow.parameterizedBy(settingsClass)).initializer( + "dataStore.data.%M { prefs -> %T(%L) }", + map, + settingsClass, + properties.joinToString(",\n") { property -> + val key = property.simpleName.asString() + "$key = prefs[${key}Key] ?: ${property.getValueAsString()}" + }, + ) + .addModifiers(KModifier.OVERRIDE) + .build() + } + + /** + * Generate a snapshot function for users who may not want to observe + * changes. This grabs the latest value at a specific point in time and may + * not always reflect the most up to date value + */ + private fun createGetSnapshotFunctionSpec( + settingsClass: ClassName, + properties: List, + ): FunSpec { + val first = MemberName("kotlinx.coroutines.flow", "first") + + return FunSpec.builder("getSnapshot") + .addModifiers(KModifier.SUSPEND, KModifier.OVERRIDE) + .returns(settingsClass) + .addCode( + "val prefs = dataStore.data.%M()\nreturn %T(%L)", + first, + settingsClass, + properties.joinToString(",\n") { property -> + val key = property.simpleName.asString() + "$key = prefs[${key}Key] ?: ${property.getValueAsString()}" + }, + ).build() + } + + private fun createSaveFunctionSpec( + settingsClass: ClassName, + properties: List, + ): FunSpec { + val edit = MemberName("androidx.datastore.preferences.core", "edit") + + return FunSpec.builder("save") + .addModifiers(KModifier.SUSPEND, KModifier.OVERRIDE) + .addParameter( + "block", + LambdaTypeName.get( + returnType = settingsClass, + parameters = listOf( + ParameterSpec.builder("", settingsClass).build(), + ), + ), + ).addCode( + """ + val currentSettings = getSnapshot() + val settings = block(currentSettings) + dataStore.%M { + %L + } + + """.trimIndent(), + edit, + properties.joinToString("\n") { property -> + val key = property.simpleName.asString() + "it[${key}Key] = settings.$key" + }, + ).build() + } + + private fun createClearFunctionSpec(): FunSpec { + val edit = MemberName("androidx.datastore.preferences.core", "edit") + + return FunSpec.builder("clear") + .addModifiers(KModifier.SUSPEND, KModifier.OVERRIDE) + .addCode("dataStore.%M { it.clear() }", edit) + .build() + } + + private fun createCompanionObjectSpec(preferenceClass: ClassName): TypeSpec = + TypeSpec.companionObjectBuilder().addAnnotation( + AnnotationSpec.builder(ClassName("kotlinx.coroutines", "InternalCoroutinesApi")) + .build(), + ).addProperty( + PropertySpec.builder( + "instance", + preferenceClass.copy(nullable = true), + ) + .mutable() + .addModifiers(KModifier.PRIVATE).initializer("null") + .build(), + ).addFunction( + FunSpec.builder("getInstance") + .returns(preferenceClass) + .addParameter( + ParameterSpec.builder( + name = "dataStore", + // DataStore + type = ClassName( + "androidx.datastore.core", + "DataStore", + ).parameterizedBy( + ClassName("androidx.datastore.preferences.core", "Preferences"), + ), + ) + .build(), + ) + .addCode( + "return instance ?: %T(dataStore).also { instance = it }", + preferenceClass, + ) + .build(), + ) + .build() + + private fun createPreferenceKeyProperties(properties: List): List = + properties.map { property -> + PropertySpec.builder( + "${property.simpleName.getShortName()}Key", + ClassName("androidx.datastore.preferences.core.Preferences", "Key").parameterizedBy( + property.type.resolve().toClassName(), + ), + ) + .addModifiers(KModifier.PRIVATE) + .initializer( + getPreferenceInitializer(property = property), + ) + .build() + } + + // Example generated line: val isFirstLaunchKey = booleanPreferencesKey("isFirstLaunch") + private fun getPreferenceInitializer(property: KSPropertyDeclaration): CodeBlock { + val keyFunction = when (property.type.resolve().toClassName()) { + Boolean::class.asClassName() -> "booleanPreferencesKey" + Int::class.asClassName() -> "intPreferencesKey" + Float::class.asClassName() -> "floatPreferencesKey" + Long::class.asClassName() -> "longPreferencesKey" + Double::class.asClassName() -> "doublePreferencesKey" + String::class.asClassName() -> "stringPreferencesKey" + else -> throw IllegalArgumentException("Unsupported type: ${property.type}") + } + + return CodeBlock.of( + "%M(%S)", + MemberName( + "androidx.datastore.preferences.core", + keyFunction, + ), + property, + ) + } +} diff --git a/preferences/processor/src/commonMain/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider b/preferences/processor/src/commonMain/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider new file mode 100644 index 00000000..8b413ae1 --- /dev/null +++ b/preferences/processor/src/commonMain/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider @@ -0,0 +1 @@ +com.foreverrafs.preferences.PreferenceProcessorProvider \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index a843efd6..4aeff80d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -121,7 +121,7 @@ include(":androidApp:benchmark") include(":shared-data") include(":swipe") include(":shared-ui") -include(":core:auth") +include(":core:authentication") include(":core:analytics") include(":core:location") include(":core:logging") @@ -133,3 +133,6 @@ include(":core:database-test") include(":desktopApp") include(":feature:diary-ai") include(":feature:diary-profile") +include(":feature:diary-auth") +include(":preferences:annotation") +include(":preferences:processor") diff --git a/shared-data/build.gradle.kts b/shared-data/build.gradle.kts index e34fd8d5..eff9b408 100644 --- a/shared-data/build.gradle.kts +++ b/shared-data/build.gradle.kts @@ -43,6 +43,7 @@ kotlin { implementation(libs.kotlinx.coroutines.test) implementation(libs.openAiKotlin) implementation(libs.uuid) + implementation(projects.preferences.annotation) implementation(libs.androidx.datastore.preferences) implementation(libs.androidx.datastore.okio) implementation(libs.ktor.client.cio) @@ -58,6 +59,8 @@ kotlin { implementation(projects.core.location) implementation(projects.core.database) } + + kotlin.srcDir("build/generated/ksp/metadata/commonMain/kotlin") } androidMain { @@ -118,3 +121,17 @@ android { sourceCompatibility = JavaVersion.VERSION_17 } } + +dependencies { + add("kspCommonMainMetadata", projects.preferences.processor) +} + +afterEvaluate { + tasks { + withType> { + if (name != "kspCommonMainKotlinMetadata") { + dependsOn("kspCommonMainKotlinMetadata") + } + } + } +} diff --git a/shared-data/src/commonMain/kotlin/com/foreverrafs/superdiary/di/Modules.kt b/shared-data/src/commonMain/kotlin/com/foreverrafs/superdiary/di/Modules.kt index a9bb0cc8..e922460e 100644 --- a/shared-data/src/commonMain/kotlin/com/foreverrafs/superdiary/di/Modules.kt +++ b/shared-data/src/commonMain/kotlin/com/foreverrafs/superdiary/di/Modules.kt @@ -1,7 +1,11 @@ package com.foreverrafs.superdiary.di +import androidx.datastore.preferences.core.PreferenceDataStoreFactory +import com.foreverrafs.preferences.DiaryPreference +import com.foreverrafs.preferences.DiaryPreferenceImpl import com.foreverrafs.superdiary.core.analytics.AnalyticsTracker import com.foreverrafs.superdiary.core.logging.AggregateLogger +import com.foreverrafs.superdiary.data.DataStorePathResolver import com.foreverrafs.superdiary.data.datasource.LocalDataSource import com.foreverrafs.superdiary.data.datasource.RemoteDataSource import com.foreverrafs.superdiary.database.di.databaseModule @@ -23,8 +27,7 @@ import com.foreverrafs.superdiary.domain.usecase.SearchDiaryByEntryUseCase import com.foreverrafs.superdiary.domain.usecase.UpdateDiaryUseCase import com.foreverrafs.superdiary.domain.validator.DiaryValidator import com.foreverrafs.superdiary.domain.validator.DiaryValidatorImpl -import com.foreverrafs.superdiary.utils.DiaryPreference -import com.foreverrafs.superdiary.utils.DiaryPreferenceImpl +import kotlinx.coroutines.InternalCoroutinesApi import kotlinx.datetime.Clock import org.koin.core.module.Module import org.koin.core.module.dsl.bind @@ -32,6 +35,7 @@ import org.koin.core.module.dsl.factoryOf import org.koin.core.module.dsl.singleOf import org.koin.dsl.module +@OptIn(InternalCoroutinesApi::class) val useCaseModule = module { includes(databaseModule()) @@ -44,8 +48,11 @@ val useCaseModule = module { factory { DiaryPreferenceImpl.getInstance( - dataStorePathResolver = get(), - logger = get(), + dataStore = PreferenceDataStoreFactory.createWithPath { + get().resolve( + "diary_preferences.preferences_pb", + ) + }, ) } factoryOf(::DiaryValidatorImpl) { bind() } diff --git a/shared-data/src/commonMain/kotlin/com/foreverrafs/superdiary/utils/DiaryPreference.kt b/shared-data/src/commonMain/kotlin/com/foreverrafs/superdiary/utils/DiaryPreference.kt deleted file mode 100644 index 427b7934..00000000 --- a/shared-data/src/commonMain/kotlin/com/foreverrafs/superdiary/utils/DiaryPreference.kt +++ /dev/null @@ -1,135 +0,0 @@ -package com.foreverrafs.superdiary.utils - -import androidx.datastore.core.DataStore -import androidx.datastore.preferences.core.PreferenceDataStoreFactory -import androidx.datastore.preferences.core.Preferences -import androidx.datastore.preferences.core.booleanPreferencesKey -import androidx.datastore.preferences.core.edit -import com.foreverrafs.superdiary.core.logging.AggregateLogger -import com.foreverrafs.superdiary.data.DataStorePathResolver -import kotlin.concurrent.Volatile -import kotlinx.coroutines.InternalCoroutinesApi -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.internal.SynchronizedObject -import kotlinx.coroutines.internal.synchronized - -interface DiaryPreference { - val settings: Flow - suspend fun save(block: (DiarySettings) -> DiarySettings) - suspend fun getSnapshot(): DiarySettings - suspend fun clear() -} - -class DiaryPreferenceImpl private constructor( - private val filename: String, - private val dataStorePathResolver: DataStorePathResolver, - private val dataStore: DataStore = PreferenceDataStoreFactory.createWithPath { - dataStorePathResolver.resolve(filename) - }, - private val logger: AggregateLogger, -) : DiaryPreference { - - private val isFirstLaunchKey = booleanPreferencesKey("isFirstLaunch") - private val showWeeklySummaryKey = booleanPreferencesKey("showWeeklySummary") - private val showAtAGlanceKey = booleanPreferencesKey("showAtAGlance") - private val showLatestEntriesKey = booleanPreferencesKey("showLatestEntries") - private val showLocationPermissionDialogKey = - booleanPreferencesKey("showLocationPermissionDialog") - private val showBiometricAuthDialogKey = booleanPreferencesKey("showBiometricAuthDialog") - private val isBiometricAuthEnabledKey = booleanPreferencesKey("isBiometricsAuthEnabled") - - override val settings: Flow = dataStore.data.map { - DiarySettings( - isFirstLaunch = it[isFirstLaunchKey] ?: true, - showWeeklySummary = it[showWeeklySummaryKey] ?: true, - showAtAGlance = it[showAtAGlanceKey] ?: true, - showLatestEntries = it[showLatestEntriesKey] ?: true, - showLocationPermissionDialog = it[showLocationPermissionDialogKey] ?: true, - showBiometricAuthDialog = it[showBiometricAuthDialogKey] ?: true, - isBiometricAuthEnabled = it[isBiometricAuthEnabledKey] ?: false, - ) - } - - override suspend fun getSnapshot(): DiarySettings { - val prefs = dataStore.data.first() - - return DiarySettings( - isFirstLaunch = prefs[isFirstLaunchKey] ?: false, - showWeeklySummary = prefs[showWeeklySummaryKey] ?: true, - showAtAGlance = prefs[showAtAGlanceKey] ?: true, - showLatestEntries = prefs[showLatestEntriesKey] ?: true, - showLocationPermissionDialog = prefs[showLocationPermissionDialogKey] ?: false, - showBiometricAuthDialog = prefs[showBiometricAuthDialogKey] ?: true, - isBiometricAuthEnabled = prefs[isBiometricAuthEnabledKey] ?: false, - ) - } - - override suspend fun save(block: (DiarySettings) -> DiarySettings) { - val currentSettings = getSnapshot() - val settings = block(currentSettings) - - dataStore.edit { - it[isFirstLaunchKey] = settings.isFirstLaunch - it[showWeeklySummaryKey] = settings.showWeeklySummary - it[showAtAGlanceKey] = settings.showAtAGlance - it[showLatestEntriesKey] = settings.showLatestEntries - it[showLocationPermissionDialogKey] = settings.showLocationPermissionDialog - it[showBiometricAuthDialogKey] = settings.showBiometricAuthDialog - it[isBiometricAuthEnabledKey] = settings.isBiometricAuthEnabled - } - logger.d(Tag) { - "Settings saved: $settings" - } - } - - override suspend fun clear() { - dataStore.edit { - it.clear() - } - } - - @OptIn(InternalCoroutinesApi::class) - companion object { - @Volatile - private var instance: DiaryPreference? = null - private val lock = SynchronizedObject() - - fun getInstance( - filename: String = "datastore.preferences_pb", - dataStorePathResolver: DataStorePathResolver, - logger: AggregateLogger, - ) = synchronized(lock) { - instance ?: DiaryPreferenceImpl( - filename = filename, - dataStorePathResolver = dataStorePathResolver, - logger = logger, - ).also { instance = it } - } - - private val Tag = DiaryPreference::class.simpleName.orEmpty() - } -} - -data class DiarySettings( - val isFirstLaunch: Boolean, - val showWeeklySummary: Boolean, - val showAtAGlance: Boolean, - val showLatestEntries: Boolean, - val isBiometricAuthEnabled: Boolean, - val showLocationPermissionDialog: Boolean, - val showBiometricAuthDialog: Boolean, -) { - companion object { - val Empty: DiarySettings = DiarySettings( - isFirstLaunch = true, - showWeeklySummary = true, - showAtAGlance = true, - showLatestEntries = true, - showLocationPermissionDialog = true, - showBiometricAuthDialog = true, - isBiometricAuthEnabled = false, - ) - } -} diff --git a/shared-data/src/commonMain/kotlin/com/foreverrafs/superdiary/utils/DiarySettings.kt b/shared-data/src/commonMain/kotlin/com/foreverrafs/superdiary/utils/DiarySettings.kt new file mode 100644 index 00000000..8d282d97 --- /dev/null +++ b/shared-data/src/commonMain/kotlin/com/foreverrafs/superdiary/utils/DiarySettings.kt @@ -0,0 +1,40 @@ +package com.foreverrafs.superdiary.utils + +import com.foreverrafs.preferences.Preference +import com.foreverrafs.preferences.PreferenceKey + +@Preference("DiaryPreference") +data class DiarySettings( + @PreferenceKey.Boolean(default = true) + val isFirstLaunch: Boolean, + + @PreferenceKey.Boolean(default = true) + val showWeeklySummary: Boolean, + + @PreferenceKey.Boolean(default = true) + val showAtAGlance: Boolean, + + @PreferenceKey.Boolean(default = true) + val showLatestEntries: Boolean, + + @PreferenceKey.Boolean(default = false) + val isBiometricAuthEnabled: Boolean, + + @PreferenceKey.Boolean(default = true) + val showLocationPermissionDialog: Boolean, + + @PreferenceKey.Boolean(default = true) + val showBiometricAuthDialog: Boolean, +) { + companion object { + val Empty: DiarySettings = DiarySettings( + isFirstLaunch = true, + showWeeklySummary = true, + showAtAGlance = true, + showLatestEntries = true, + isBiometricAuthEnabled = false, + showLocationPermissionDialog = true, + showBiometricAuthDialog = true, + ) + } +} diff --git a/shared-data/src/commonTest/kotlin/com/foreverrafs/superdiary/utils/DiaryPreferenceTest.kt b/shared-data/src/commonTest/kotlin/com/foreverrafs/superdiary/utils/DiaryPreferenceTest.kt index d4306f4e..938745f9 100644 --- a/shared-data/src/commonTest/kotlin/com/foreverrafs/superdiary/utils/DiaryPreferenceTest.kt +++ b/shared-data/src/commonTest/kotlin/com/foreverrafs/superdiary/utils/DiaryPreferenceTest.kt @@ -1,17 +1,20 @@ package com.foreverrafs.superdiary.utils +import androidx.datastore.preferences.core.PreferenceDataStoreFactory import app.cash.turbine.test import assertk.assertThat import assertk.assertions.isEqualTo import assertk.assertions.isTrue +import com.foreverrafs.preferences.DiaryPreference +import com.foreverrafs.preferences.DiaryPreferenceImpl import com.foreverrafs.superdiary.common.coroutines.TestAppDispatchers -import com.foreverrafs.superdiary.core.logging.AggregateLogger import com.foreverrafs.superdiary.data.DataStorePathResolver import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.InternalCoroutinesApi import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain @@ -25,11 +28,13 @@ class DiaryPreferenceTest { // The path used here is compatible with all platforms "/tmp/Test/TempPath/$filename".toPath() } + + @OptIn(InternalCoroutinesApi::class) private val diaryPreference: DiaryPreference = DiaryPreferenceImpl.getInstance( - filename = "superdiary.preferences_pb", - dataStorePathResolver = dataStorePathResolver, - logger = AggregateLogger(emptyList()), + PreferenceDataStoreFactory.createWithPath { + dataStorePathResolver.resolve("test.preferences_pb") + }, ) @BeforeTest @@ -95,15 +100,18 @@ class DiaryPreferenceTest { assertThat(finalState).isEqualTo(updatedSettings) } + @OptIn(InternalCoroutinesApi::class) @Test fun `Should return the same instance of diary preference`() = runTest { val first = DiaryPreferenceImpl.getInstance( - dataStorePathResolver = dataStorePathResolver, - logger = AggregateLogger(emptyList()), + PreferenceDataStoreFactory.createWithPath { + dataStorePathResolver.resolve("test.preferences_pb") + }, ) val second = DiaryPreferenceImpl.getInstance( - dataStorePathResolver = dataStorePathResolver, - logger = AggregateLogger(emptyList()), + PreferenceDataStoreFactory.createWithPath { + dataStorePathResolver.resolve("test.preferences_pb") + }, ) assertThat(first).isEqualTo(second) diff --git a/shared-ui/build.gradle.kts b/shared-ui/build.gradle.kts index cf08c8fb..fe2339aa 100644 --- a/shared-ui/build.gradle.kts +++ b/shared-ui/build.gradle.kts @@ -36,7 +36,7 @@ kotlin { export(projects.core.analytics) export(projects.core.logging) export(projects.core.location) - export(projects.core.auth) + export(projects.core.authentication) } } @@ -69,16 +69,17 @@ kotlin { implementation(libs.coil3.multiplatform) implementation(libs.ktor.client.core) dependencies { - implementation("com.valentinilk.shimmer:compose-shimmer:1.3.1") + implementation("com.valentinilk.shimmer:compose-shimmer:1.3.2") } implementation(libs.koin.compose.viewmodel) - api(projects.core.auth) + api(projects.core.authentication) api(projects.core.analytics) api(projects.core.location) api(projects.core.logging) api(projects.designSystem) implementation(projects.feature.diaryAi) implementation(projects.feature.diaryProfile) + implementation(projects.feature.diaryAuth) } } diff --git a/shared-ui/src/androidMain/kotlin/com/foreverrafs/superdiary/ui/AndroidPreviews.kt b/shared-ui/src/androidMain/kotlin/com/foreverrafs/superdiary/ui/AndroidPreviews.kt index 49585834..331ce166 100644 --- a/shared-ui/src/androidMain/kotlin/com/foreverrafs/superdiary/ui/AndroidPreviews.kt +++ b/shared-ui/src/androidMain/kotlin/com/foreverrafs/superdiary/ui/AndroidPreviews.kt @@ -17,13 +17,6 @@ import com.foreverrafs.superdiary.domain.model.Diary import com.foreverrafs.superdiary.domain.model.Streak import com.foreverrafs.superdiary.profile.presentation.ProfileScreenViewData import com.foreverrafs.superdiary.profile.presentation.screen.ProfileScreenContent -import com.foreverrafs.superdiary.ui.feature.auth.login.BiometricLoginScreenState -import com.foreverrafs.superdiary.ui.feature.auth.login.screen.BiometricLoginScreenContent -import com.foreverrafs.superdiary.ui.feature.auth.login.screen.LoginScreenContent -import com.foreverrafs.superdiary.ui.feature.auth.login.screen.LoginViewState -import com.foreverrafs.superdiary.ui.feature.auth.register.screen.RegisterScreenContent -import com.foreverrafs.superdiary.ui.feature.auth.register.screen.RegisterScreenState -import com.foreverrafs.superdiary.ui.feature.auth.register.screen.RegistrationConfirmationScreen import com.foreverrafs.superdiary.ui.feature.creatediary.screen.CreateDiaryScreenContent import com.foreverrafs.superdiary.ui.feature.dashboard.DashboardViewModel import com.foreverrafs.superdiary.ui.feature.dashboard.screen.DashboardScreenContent @@ -77,17 +70,17 @@ fun TestAppContainer( } } -@Composable -@PreviewSuperDiary -private fun BiometricLoginScreen() { - SuperDiaryTheme { - BiometricLoginScreenContent( - viewState = BiometricLoginScreenState.Idle, - onBiometricAuthSuccess = {}, - showBiometricAuthErrorDialog = false, - ) - } -} +// @Composable +// @PreviewSuperDiary +// private fun BiometricLoginScreen() { +// SuperDiaryTheme { +// BiometricLoginScreenContent( +// viewState = BiometricLoginScreenState.Idle, +// onBiometricAuthSuccess = {}, +// showBiometricAuthErrorDialog = false, +// ) +// } +// } @PreviewSuperDiary @Composable @@ -125,17 +118,17 @@ private fun ProfileScreenPreviewLogoutDialog() { ) } -@Composable -@PreviewSuperDiary -private fun RegistrationConfirmationPreview() { - SuperDiaryTheme { - SuperDiaryTheme { - Surface(color = MaterialTheme.colorScheme.background) { - RegistrationConfirmationScreen() - } - } - } -} +// @Composable +// @PreviewSuperDiary +// private fun RegistrationConfirmationPreview() { +// SuperDiaryTheme { +// SuperDiaryTheme { +// Surface(color = MaterialTheme.colorScheme.background) { +// RegistrationConfirmationScreen() +// } +// } +// } +// } @PreviewSuperDiary @Composable @@ -474,34 +467,34 @@ private fun DetailPreview() { } } -@Composable -@PreviewSuperDiary -private fun LoginPreview() { - SuperDiaryTheme { - LoginScreenContent( - onLoginWithGoogle = {}, - onLoginClick = { _, _ -> }, - onRegisterClick = {}, - viewState = LoginViewState.Idle, - onSignInSuccess = {}, - isFromDeeplink = false, - onResetPasswordClick = {}, - ) - } -} - -@Composable -@PreviewSuperDiary -private fun RegisterPreview() { - SuperDiaryTheme { - RegisterScreenContent( - onRegisterClick = { _, _, _ -> }, - viewState = RegisterScreenState.Idle, - onRegisterSuccess = {}, - onLoginClick = {}, - ) - } -} +// @Composable +// @PreviewSuperDiary +// private fun LoginPreview() { +// SuperDiaryTheme { +// LoginScreenContent( +// onLoginWithGoogle = {}, +// onLoginClick = { _, _ -> }, +// onRegisterClick = {}, +// viewState = LoginViewState.Idle, +// onSignInSuccess = {}, +// isFromDeeplink = false, +// onResetPasswordClick = {}, +// ) +// } +// } +// +// @Composable +// @PreviewSuperDiary +// private fun RegisterPreview() { +// SuperDiaryTheme { +// RegisterScreenContent( +// onRegisterClick = { _, _, _ -> }, +// viewState = RegisterScreenState.Idle, +// onRegisterSuccess = {}, +// onLoginClick = {}, +// ) +// } +// } @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, name = "Night") @Preview(uiMode = Configuration.UI_MODE_NIGHT_NO, name = "Day") diff --git a/shared-ui/src/androidMain/kotlin/com/foreverrafs/superdiary/ui/SuperDiaryBackPressHandler.android.kt b/shared-ui/src/androidMain/kotlin/com/foreverrafs/superdiary/ui/SuperDiaryBackPressHandler.android.kt deleted file mode 100644 index 29354e9c..00000000 --- a/shared-ui/src/androidMain/kotlin/com/foreverrafs/superdiary/ui/SuperDiaryBackPressHandler.android.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.foreverrafs.superdiary.ui - -import androidx.activity.compose.BackHandler -import androidx.compose.runtime.Composable - -@Composable -actual fun BackHandler(onBack: () -> Unit) { - BackHandler { - onBack() - } -} diff --git a/shared-ui/src/commonMain/composeResources/values/strings.xml b/shared-ui/src/commonMain/composeResources/values/strings.xml index f48c5fe8..28330a53 100644 --- a/shared-ui/src/commonMain/composeResources/values/strings.xml +++ b/shared-ui/src/commonMain/composeResources/values/strings.xml @@ -27,19 +27,6 @@ Retry Show All Diary Deleted! - Username - Password - Login - Welcome back! - Create an account - Don't have an account yet? - Register - Reset password - Reset your password - Continue with Google - Open email - - A password reset email will be sent to $1 if it exists in our records. Navigate back diff --git a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/App.kt b/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/App.kt index 3d91ea37..d19c9384 100644 --- a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/App.kt +++ b/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/App.kt @@ -42,14 +42,14 @@ import coil3.memory.MemoryCache import coil3.request.CachePolicy import coil3.request.crossfade import com.foreverrafs.auth.model.UserInfo +import com.foreverrafs.superdiary.auth.login.screen.BiometricLoginScreen +import com.foreverrafs.superdiary.auth.login.screen.LoginScreen +import com.foreverrafs.superdiary.auth.register.DeeplinkContainer +import com.foreverrafs.superdiary.auth.register.screen.RegisterScreen +import com.foreverrafs.superdiary.auth.register.screen.RegistrationConfirmationScreen +import com.foreverrafs.superdiary.auth.reset.SendPasswordResetEmailScreen import com.foreverrafs.superdiary.design.style.SuperDiaryTheme import com.foreverrafs.superdiary.profile.presentation.screen.ProfileScreen -import com.foreverrafs.superdiary.ui.feature.auth.login.screen.BiometricLoginScreen -import com.foreverrafs.superdiary.ui.feature.auth.login.screen.LoginScreen -import com.foreverrafs.superdiary.ui.feature.auth.register.DeeplinkContainer -import com.foreverrafs.superdiary.ui.feature.auth.register.screen.RegisterScreenContent -import com.foreverrafs.superdiary.ui.feature.auth.register.screen.RegistrationConfirmationScreen -import com.foreverrafs.superdiary.ui.feature.auth.reset.SendPasswordResetEmailScreen import com.foreverrafs.superdiary.ui.feature.creatediary.screen.CreateDiaryScreen import com.foreverrafs.superdiary.ui.feature.details.screen.DetailScreenContent import com.foreverrafs.superdiary.ui.feature.diarylist.screen.DiaryListScreen @@ -188,7 +188,7 @@ private fun SuperDiaryNavHost( } animatedComposable { - RegisterScreenContent( + RegisterScreen( onLoginClick = { navController.navigate(AppRoute.LoginScreen) { popUpTo(navController.graph.startDestinationId) { diff --git a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/AppViewModel.kt b/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/AppViewModel.kt index 85d4dfba..e1e4f412 100644 --- a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/AppViewModel.kt +++ b/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/AppViewModel.kt @@ -5,11 +5,11 @@ import androidx.lifecycle.viewModelScope import com.foreverrafs.auth.AuthApi import com.foreverrafs.auth.model.SessionInfo import com.foreverrafs.auth.model.UserInfo +import com.foreverrafs.preferences.DiaryPreference +import com.foreverrafs.superdiary.auth.register.DeeplinkContainer import com.foreverrafs.superdiary.common.utils.AppCoroutineDispatchers import com.foreverrafs.superdiary.core.logging.AggregateLogger import com.foreverrafs.superdiary.data.Result -import com.foreverrafs.superdiary.ui.feature.auth.register.DeeplinkContainer -import com.foreverrafs.superdiary.utils.DiaryPreference import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow diff --git a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/di/Module.kt b/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/di/Module.kt index 4ade97a6..6d461674 100644 --- a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/di/Module.kt +++ b/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/di/Module.kt @@ -2,6 +2,11 @@ package com.foreverrafs.superdiary.ui.di import com.foreverrafs.auth.di.authModule import com.foreverrafs.superdiary.ai.di.diaryAiModule +import com.foreverrafs.superdiary.auth.login.BiometricLoginScreenViewModel +import com.foreverrafs.superdiary.auth.login.LoginScreenViewModel +import com.foreverrafs.superdiary.auth.register.DeeplinkContainer +import com.foreverrafs.superdiary.auth.register.RegisterScreenViewModel +import com.foreverrafs.superdiary.auth.reset.PasswordResetViewModel import com.foreverrafs.superdiary.common.utils.di.utilsModule import com.foreverrafs.superdiary.core.analytics.AnalyticsTracker import com.foreverrafs.superdiary.core.location.di.locationModule @@ -11,11 +16,6 @@ import com.foreverrafs.superdiary.di.platformModule import com.foreverrafs.superdiary.di.useCaseModule import com.foreverrafs.superdiary.profile.di.profileModule import com.foreverrafs.superdiary.ui.AppViewModel -import com.foreverrafs.superdiary.ui.feature.auth.login.BiometricLoginScreenViewModel -import com.foreverrafs.superdiary.ui.feature.auth.login.LoginScreenViewModel -import com.foreverrafs.superdiary.ui.feature.auth.register.DeeplinkContainer -import com.foreverrafs.superdiary.ui.feature.auth.register.RegisterScreenViewModel -import com.foreverrafs.superdiary.ui.feature.auth.reset.PasswordResetViewModel import com.foreverrafs.superdiary.ui.feature.creatediary.CreateDiaryViewModel import com.foreverrafs.superdiary.ui.feature.dashboard.DashboardViewModel import com.foreverrafs.superdiary.ui.feature.details.DetailsViewModel diff --git a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/register/RegisterScreenViewModel.kt b/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/register/RegisterScreenViewModel.kt deleted file mode 100644 index 9c610b97..00000000 --- a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/auth/register/RegisterScreenViewModel.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.foreverrafs.superdiary.ui.feature.auth.register - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.foreverrafs.auth.AuthApi -import com.foreverrafs.superdiary.common.utils.AppCoroutineDispatchers -import com.foreverrafs.superdiary.ui.feature.auth.register.screen.RegisterScreenState -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update -import kotlinx.coroutines.launch - -class RegisterScreenViewModel( - private val authApi: AuthApi, - private val coroutineDispatchers: AppCoroutineDispatchers, -) : ViewModel() { - private val _viewState: MutableStateFlow = - MutableStateFlow(RegisterScreenState.Idle) - - val viewState = _viewState - .asStateFlow() - - fun onRegisterClick( - name: String, - email: String, - password: String, - ) = - viewModelScope.launch(coroutineDispatchers.main) { - _viewState.update { - RegisterScreenState.Processing - } - - when ( - val result = authApi.register( - name = name, - email = email, - password = password, - ) - ) { - is AuthApi.RegistrationStatus.Error -> _viewState.update { - RegisterScreenState.Error( - error = result.exception, - ) - } - - is AuthApi.RegistrationStatus.Success -> _viewState.update { - RegisterScreenState.Success - } - } - } -} diff --git a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/creatediary/CreateDiaryViewModel.kt b/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/creatediary/CreateDiaryViewModel.kt index bc021e0c..d744ebea 100644 --- a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/creatediary/CreateDiaryViewModel.kt +++ b/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/creatediary/CreateDiaryViewModel.kt @@ -2,6 +2,7 @@ package com.foreverrafs.superdiary.ui.feature.creatediary import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.foreverrafs.preferences.DiaryPreference import com.foreverrafs.superdiary.ai.api.DiaryAI import com.foreverrafs.superdiary.core.location.LocationManager import com.foreverrafs.superdiary.core.location.permission.LocationPermissionManager @@ -11,7 +12,6 @@ import com.foreverrafs.superdiary.core.logging.AggregateLogger import com.foreverrafs.superdiary.domain.model.Diary import com.foreverrafs.superdiary.domain.usecase.AddDiaryUseCase import com.foreverrafs.superdiary.ui.feature.creatediary.screen.CreateDiaryScreenState -import com.foreverrafs.superdiary.utils.DiaryPreference import com.foreverrafs.superdiary.utils.DiarySettings import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow diff --git a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/creatediary/screen/CreateDiaryScreenContent.kt b/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/creatediary/screen/CreateDiaryScreenContent.kt index 0c7c8fd2..25ca0bee 100644 --- a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/creatediary/screen/CreateDiaryScreenContent.kt +++ b/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/creatediary/screen/CreateDiaryScreenContent.kt @@ -38,10 +38,10 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.foreverrafs.auth.model.UserInfo import com.foreverrafs.superdiary.core.location.permission.PermissionState +import com.foreverrafs.superdiary.design.components.BackHandler import com.foreverrafs.superdiary.design.components.ConfirmSaveDialog import com.foreverrafs.superdiary.design.components.LocationRationaleDialog import com.foreverrafs.superdiary.design.components.SuperDiaryAppBar -import com.foreverrafs.superdiary.ui.BackHandler import com.foreverrafs.superdiary.ui.feature.creatediary.components.RichTextStyleRow import com.mohamedrejeb.richeditor.model.RichTextState import com.mohamedrejeb.richeditor.model.rememberRichTextState diff --git a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/dashboard/DashboardViewModel.kt b/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/dashboard/DashboardViewModel.kt index 4fcea825..4a4c759c 100644 --- a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/dashboard/DashboardViewModel.kt +++ b/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/dashboard/DashboardViewModel.kt @@ -3,6 +3,7 @@ package com.foreverrafs.superdiary.ui.feature.dashboard import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.foreverrafs.auth.BiometricAuth +import com.foreverrafs.preferences.DiaryPreference import com.foreverrafs.superdiary.ai.api.DiaryAI import com.foreverrafs.superdiary.core.logging.AggregateLogger import com.foreverrafs.superdiary.data.Result @@ -15,7 +16,6 @@ import com.foreverrafs.superdiary.domain.usecase.CalculateStreakUseCase import com.foreverrafs.superdiary.domain.usecase.GetAllDiariesUseCase import com.foreverrafs.superdiary.domain.usecase.GetWeeklySummaryUseCase import com.foreverrafs.superdiary.domain.usecase.UpdateDiaryUseCase -import com.foreverrafs.superdiary.utils.DiaryPreference import com.foreverrafs.superdiary.utils.DiarySettings import com.foreverrafs.superdiary.utils.toDate import kotlinx.coroutines.flow.Flow diff --git a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/diarychat/screen/DiaryChatScreenContent.kt b/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/diarychat/screen/DiaryChatScreenContent.kt index 5a69b211..adf65db4 100644 --- a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/diarychat/screen/DiaryChatScreenContent.kt +++ b/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/diarychat/screen/DiaryChatScreenContent.kt @@ -7,6 +7,8 @@ import androidx.compose.animation.core.infiniteRepeatable import androidx.compose.animation.core.rememberInfiniteTransition import androidx.compose.animation.core.tween import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -51,6 +53,7 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.input.ImeAction @@ -73,9 +76,17 @@ fun DiaryChatScreenContent( onQueryDiaries: (query: String) -> Unit = {}, ) { val focusRequester = remember { FocusRequester() } + val keyboardController = LocalSoftwareKeyboardController.current Column( modifier = modifier + .clickable( + indication = null, + onClick = { + keyboardController?.hide() + }, + interactionSource = MutableInteractionSource(), + ) .fillMaxSize() .animateContentSize() .imePadding() diff --git a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/diarylist/screen/DiaryListScreenContent.kt b/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/diarylist/screen/DiaryListScreenContent.kt index 13d49e28..676d9515 100644 --- a/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/diarylist/screen/DiaryListScreenContent.kt +++ b/shared-ui/src/commonMain/kotlin/com/foreverrafs/superdiary/ui/feature/diarylist/screen/DiaryListScreenContent.kt @@ -71,10 +71,10 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.foreverrafs.superdiary.design.components.BackHandler import com.foreverrafs.superdiary.design.components.ConfirmDeleteDialog import com.foreverrafs.superdiary.design.components.SuperDiaryAppBar import com.foreverrafs.superdiary.domain.model.Diary -import com.foreverrafs.superdiary.ui.BackHandler import com.foreverrafs.superdiary.ui.feature.diarylist.DiaryFilters import com.foreverrafs.superdiary.ui.feature.diarylist.DiaryListActions import com.foreverrafs.superdiary.ui.feature.diarylist.components.DiaryFilterSheet diff --git a/shared-ui/src/commonTest/kotlin/com/foreverrafs/superdiary/ui/creatediary/CreateDiaryViewModelTest.kt b/shared-ui/src/commonTest/kotlin/com/foreverrafs/superdiary/ui/creatediary/CreateDiaryViewModelTest.kt index 6b2e9b07..7462cef5 100644 --- a/shared-ui/src/commonTest/kotlin/com/foreverrafs/superdiary/ui/creatediary/CreateDiaryViewModelTest.kt +++ b/shared-ui/src/commonTest/kotlin/com/foreverrafs/superdiary/ui/creatediary/CreateDiaryViewModelTest.kt @@ -5,6 +5,7 @@ import assertk.assertThat import assertk.assertions.isInstanceOf import assertk.assertions.isNotNull import assertk.assertions.isNull +import com.foreverrafs.preferences.DiaryPreference import com.foreverrafs.superdiary.ai.api.DiaryAI import com.foreverrafs.superdiary.common.coroutines.TestAppDispatchers import com.foreverrafs.superdiary.core.location.Location @@ -17,7 +18,6 @@ import com.foreverrafs.superdiary.domain.repository.DataSource import com.foreverrafs.superdiary.domain.usecase.AddDiaryUseCase import com.foreverrafs.superdiary.ui.creatediary.FakePermissionsControllerWrapper.ActionPerformed.ProvidePermission import com.foreverrafs.superdiary.ui.feature.creatediary.CreateDiaryViewModel -import com.foreverrafs.superdiary.utils.DiaryPreference import com.foreverrafs.superdiary.utils.DiarySettings import dev.mokkery.answering.returns import dev.mokkery.every diff --git a/shared-ui/src/commonTest/kotlin/com/foreverrafs/superdiary/ui/dashboard/DashboardViewModelTest.kt b/shared-ui/src/commonTest/kotlin/com/foreverrafs/superdiary/ui/dashboard/DashboardViewModelTest.kt index 8c3ee9f1..b2894844 100644 --- a/shared-ui/src/commonTest/kotlin/com/foreverrafs/superdiary/ui/dashboard/DashboardViewModelTest.kt +++ b/shared-ui/src/commonTest/kotlin/com/foreverrafs/superdiary/ui/dashboard/DashboardViewModelTest.kt @@ -10,6 +10,7 @@ import assertk.assertions.isNotNull import assertk.assertions.isNull import assertk.assertions.isTrue import com.foreverrafs.auth.BiometricAuth +import com.foreverrafs.preferences.DiaryPreference import com.foreverrafs.superdiary.ai.api.DiaryAI import com.foreverrafs.superdiary.common.coroutines.TestAppDispatchers import com.foreverrafs.superdiary.core.logging.AggregateLogger @@ -23,7 +24,6 @@ import com.foreverrafs.superdiary.domain.usecase.GetAllDiariesUseCase import com.foreverrafs.superdiary.domain.usecase.GetWeeklySummaryUseCase import com.foreverrafs.superdiary.domain.usecase.UpdateDiaryUseCase import com.foreverrafs.superdiary.ui.feature.dashboard.DashboardViewModel -import com.foreverrafs.superdiary.utils.DiaryPreference import com.foreverrafs.superdiary.utils.DiarySettings import dev.mokkery.answering.returns import dev.mokkery.answering.throws diff --git a/shared-ui/src/commonTest/kotlin/com/foreverrafs/superdiary/ui/dashboard/FakeDiaryPreference.kt b/shared-ui/src/commonTest/kotlin/com/foreverrafs/superdiary/ui/dashboard/FakeDiaryPreference.kt index f56c8608..fb3e331a 100644 --- a/shared-ui/src/commonTest/kotlin/com/foreverrafs/superdiary/ui/dashboard/FakeDiaryPreference.kt +++ b/shared-ui/src/commonTest/kotlin/com/foreverrafs/superdiary/ui/dashboard/FakeDiaryPreference.kt @@ -1,6 +1,6 @@ package com.foreverrafs.superdiary.ui.dashboard -import com.foreverrafs.superdiary.utils.DiaryPreference +import com.foreverrafs.preferences.DiaryPreference import com.foreverrafs.superdiary.utils.DiarySettings import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf diff --git a/shared-ui/src/commonTest/kotlin/com/foreverrafs/superdiary/ui/diarychat/DiaryChatViewModelTest.kt b/shared-ui/src/commonTest/kotlin/com/foreverrafs/superdiary/ui/diarychat/DiaryChatViewModelTest.kt index 97cf26fe..4fa1c10a 100644 --- a/shared-ui/src/commonTest/kotlin/com/foreverrafs/superdiary/ui/diarychat/DiaryChatViewModelTest.kt +++ b/shared-ui/src/commonTest/kotlin/com/foreverrafs/superdiary/ui/diarychat/DiaryChatViewModelTest.kt @@ -10,11 +10,11 @@ import com.foreverrafs.superdiary.ai.domain.repository.DiaryAiRepository import com.foreverrafs.superdiary.ai.domain.usecase.GetChatMessagesUseCase import com.foreverrafs.superdiary.ai.domain.usecase.SaveChatMessageUseCase import com.foreverrafs.superdiary.common.coroutines.TestAppDispatchers +import com.foreverrafs.superdiary.common.coroutines.awaitUntil import com.foreverrafs.superdiary.core.logging.AggregateLogger import com.foreverrafs.superdiary.domain.model.Diary import com.foreverrafs.superdiary.domain.repository.DataSource import com.foreverrafs.superdiary.domain.usecase.GetAllDiariesUseCase -import com.foreverrafs.superdiary.ui.awaitUntil import com.foreverrafs.superdiary.ui.feature.diarychat.DiaryChatViewModel import dev.mokkery.answering.returns import dev.mokkery.every diff --git a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DashboardScreenSnapshotTest_Dashboard_Screen[PIXEL_6_DARK].png b/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DashboardScreenSnapshotTest_Dashboard_Screen[PIXEL_6_DARK].png deleted file mode 100644 index fe18bed5..00000000 --- a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DashboardScreenSnapshotTest_Dashboard_Screen[PIXEL_6_DARK].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:491dcec8d7554eadfe9c2ec9b73bcb016cbce3ff2bf45f62b6fa36f2c9ade15f -size 62538 diff --git a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DashboardScreenSnapshotTest_Dashboard_Screen[PIXEL_6_LIGHT].png b/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DashboardScreenSnapshotTest_Dashboard_Screen[PIXEL_6_LIGHT].png deleted file mode 100644 index a490a1c8..00000000 --- a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DashboardScreenSnapshotTest_Dashboard_Screen[PIXEL_6_LIGHT].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:42fa2a0b2a351425a0e5b7625dacdd344796fbb9c0c5a6442473b178e8d05ceb -size 63152 diff --git a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DashboardScreenSnapshotTest_Dashboard_Screen_-_Hide_latest_entries[PIXEL_6_DARK].png b/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DashboardScreenSnapshotTest_Dashboard_Screen_-_Hide_latest_entries[PIXEL_6_DARK].png index 0efbd709..d4c39475 100644 --- a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DashboardScreenSnapshotTest_Dashboard_Screen_-_Hide_latest_entries[PIXEL_6_DARK].png +++ b/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DashboardScreenSnapshotTest_Dashboard_Screen_-_Hide_latest_entries[PIXEL_6_DARK].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:239cb7bfd0c5aba7c43b4c085392db6afcae9c9f3b048d07da7a816b4d3095ce -size 42025 +oid sha256:1c4ee23aec3075686d6ed37322dab0ca6f86cc2d5b4c7378a7d45a10f4db9b72 +size 42067 diff --git a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DashboardScreenSnapshotTest_Dashboard_Screen_-_Hide_latest_entries[PIXEL_6_LIGHT].png b/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DashboardScreenSnapshotTest_Dashboard_Screen_-_Hide_latest_entries[PIXEL_6_LIGHT].png index 8707e012..40a92404 100644 --- a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DashboardScreenSnapshotTest_Dashboard_Screen_-_Hide_latest_entries[PIXEL_6_LIGHT].png +++ b/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DashboardScreenSnapshotTest_Dashboard_Screen_-_Hide_latest_entries[PIXEL_6_LIGHT].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7fd065ce82a1cdb8189bd56f497062b0adae3a66e880cc8d35daa659d3934296 -size 42019 +oid sha256:f90fa6fae9d89bfd61c4bb94ef67338a760aea22011d8ecd2c99e7accf55e5d2 +size 42072 diff --git a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DashboardScreenSnapshotTest_Dashboard_Screen_-_Latest_Entries[PIXEL_6_DARK].png b/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DashboardScreenSnapshotTest_Dashboard_Screen_-_Latest_Entries[PIXEL_6_DARK].png index 6ce84f2d..fa9ae2f4 100644 --- a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DashboardScreenSnapshotTest_Dashboard_Screen_-_Latest_Entries[PIXEL_6_DARK].png +++ b/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DashboardScreenSnapshotTest_Dashboard_Screen_-_Latest_Entries[PIXEL_6_DARK].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e3b44b7ba2f401c3eb54a2cc2d6c90af3ace4838dc4b64ed370b4452463242d0 -size 68438 +oid sha256:2fb9c548b480a267fe37dec848beaf0ee42d10dce51497257677dbc3882dc829 +size 68478 diff --git a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DashboardScreenSnapshotTest_Dashboard_Screen_-_Latest_Entries[PIXEL_6_LIGHT].png b/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DashboardScreenSnapshotTest_Dashboard_Screen_-_Latest_Entries[PIXEL_6_LIGHT].png index bed0c1ad..fe03abe3 100644 --- a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DashboardScreenSnapshotTest_Dashboard_Screen_-_Latest_Entries[PIXEL_6_LIGHT].png +++ b/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DashboardScreenSnapshotTest_Dashboard_Screen_-_Latest_Entries[PIXEL_6_LIGHT].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fe237a467a5b0a672ecf1840a1b5c7393f5ee3c4825f6df9d44851fa7abb4fbf -size 69172 +oid sha256:8923578f78d49ec66c9380d23d8b54f44772313b501b20ddb4b328dccefb4afc +size 69224 diff --git a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DashboardScreenSnapshotTest_Dashboard_Screen_-_Location_permission_dialog[PIXEL_6_DARK].png b/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DashboardScreenSnapshotTest_Dashboard_Screen_-_Location_permission_dialog[PIXEL_6_DARK].png deleted file mode 100644 index 1751b180..00000000 --- a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DashboardScreenSnapshotTest_Dashboard_Screen_-_Location_permission_dialog[PIXEL_6_DARK].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b3f6254a2d02f1db9bacfc3e87813beaf7f46b728999f97961c3203cc42c9d18 -size 68688 diff --git a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DashboardScreenSnapshotTest_Dashboard_Screen_-_Location_permission_dialog[PIXEL_6_LIGHT].png b/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DashboardScreenSnapshotTest_Dashboard_Screen_-_Location_permission_dialog[PIXEL_6_LIGHT].png deleted file mode 100644 index 7a59a803..00000000 --- a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DashboardScreenSnapshotTest_Dashboard_Screen_-_Location_permission_dialog[PIXEL_6_LIGHT].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e798443c483ce099eaebef93674ad4a2fab7b69fc58b5facdd5545301b8f46e5 -size 69367 diff --git a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DiaryListSnapshotTests_Create_diary_entry[PIXEL_6_DARK].png b/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DiaryListSnapshotTests_Create_diary_entry[PIXEL_6_DARK].png deleted file mode 100644 index d1f70605..00000000 --- a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DiaryListSnapshotTests_Create_diary_entry[PIXEL_6_DARK].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b0a52ca5f6c008a4ee0e0c5699358c78091b556e44f4fbfa517d5da8ad74e7f1 -size 31657 diff --git a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DiaryListSnapshotTests_Create_diary_entry[PIXEL_6_LIGHT].png b/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DiaryListSnapshotTests_Create_diary_entry[PIXEL_6_LIGHT].png deleted file mode 100644 index d71d88b8..00000000 --- a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DiaryListSnapshotTests_Create_diary_entry[PIXEL_6_LIGHT].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1369d4c18755fa6b4321cf311e2c6a6836223a4b98a99ed002e0f2d2eac56597 -size 31496 diff --git a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DiaryListSnapshotTests_Create_diary_entry_AI_generated[PIXEL_6_DARK].png b/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DiaryListSnapshotTests_Create_diary_entry_AI_generated[PIXEL_6_DARK].png deleted file mode 100644 index e48ea694..00000000 --- a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DiaryListSnapshotTests_Create_diary_entry_AI_generated[PIXEL_6_DARK].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:65ae7d380caf19189bb27ef672f1b33629926f05c496c270616e448ae916c155 -size 36864 diff --git a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DiaryListSnapshotTests_Create_diary_entry_AI_generated[PIXEL_6_LIGHT].png b/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DiaryListSnapshotTests_Create_diary_entry_AI_generated[PIXEL_6_LIGHT].png deleted file mode 100644 index 3df82344..00000000 --- a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DiaryListSnapshotTests_Create_diary_entry_AI_generated[PIXEL_6_LIGHT].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1cca2a4b8a0e35aa6e4756e6c752840fafc0b43db1d7c39c8e589c42b1234f5c -size 36716 diff --git a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DiaryListSnapshotTests_Error_loading_diary_list_-_rafs[PIXEL_6_DARK].png b/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DiaryListSnapshotTests_Error_loading_diary_list_-_rafs[PIXEL_6_DARK].png deleted file mode 100644 index 533fb741..00000000 --- a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DiaryListSnapshotTests_Error_loading_diary_list_-_rafs[PIXEL_6_DARK].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b5b0f28cf1c785826f95ee2c3f2469440d26a95282003446e700c3c19e1b9a88 -size 25402 diff --git a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DiaryListSnapshotTests_Error_loading_diary_list_-_rafs[PIXEL_6_LIGHT].png b/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DiaryListSnapshotTests_Error_loading_diary_list_-_rafs[PIXEL_6_LIGHT].png deleted file mode 100644 index 70be6c7f..00000000 --- a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_DiaryListSnapshotTests_Error_loading_diary_list_-_rafs[PIXEL_6_LIGHT].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a30eeffcacd4c767e7c1abb057541bba748a007f05b7309f70c2db98e4dacdcc -size 27208 diff --git a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_RegisterScreenSnapshotTest_Login_Screen_-_Idle[PIXEL_6_DARK].png b/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_RegisterScreenSnapshotTest_Login_Screen_-_Idle[PIXEL_6_DARK].png deleted file mode 100644 index af1f842c..00000000 --- a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_RegisterScreenSnapshotTest_Login_Screen_-_Idle[PIXEL_6_DARK].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:49ee759ac512cf668e1b822f0e5962f58219b93ba5acd7ee403ff8ac80363b01 -size 148300 diff --git a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_RegisterScreenSnapshotTest_Login_Screen_-_Idle[PIXEL_6_LIGHT].png b/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_RegisterScreenSnapshotTest_Login_Screen_-_Idle[PIXEL_6_LIGHT].png deleted file mode 100644 index 19f06379..00000000 --- a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_RegisterScreenSnapshotTest_Login_Screen_-_Idle[PIXEL_6_LIGHT].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a9673e6b9109f07ae5c8ccdee602d45fc0c5eb72968643a10e71eedde3bad59c -size 147644 diff --git a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_RegisterScreenSnapshotTest_Login_Screen_-_Processing[PIXEL_6_DARK].png b/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_RegisterScreenSnapshotTest_Login_Screen_-_Processing[PIXEL_6_DARK].png deleted file mode 100644 index 7be0b853..00000000 --- a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_RegisterScreenSnapshotTest_Login_Screen_-_Processing[PIXEL_6_DARK].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:acc772452e6e2c433b0af9ebc86f45ed2658ad1d87d810fb82524c958272618b -size 148077 diff --git a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_RegisterScreenSnapshotTest_Login_Screen_-_Processing[PIXEL_6_LIGHT].png b/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_RegisterScreenSnapshotTest_Login_Screen_-_Processing[PIXEL_6_LIGHT].png deleted file mode 100644 index bf972a9a..00000000 --- a/shared-ui/src/test/snapshots/images/com.foreverrafs.superdiary.ui_RegisterScreenSnapshotTest_Login_Screen_-_Processing[PIXEL_6_LIGHT].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b71299e9ba1b65619742528bf8f388cba04ff502a766cb0c450e00833be06eac -size 147396