diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b4897244..933fbfe3 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -47,6 +47,7 @@ dependencies { implementation(projects.feature.mission) implementation(projects.feature.setting) implementation(projects.feature.webview) + implementation(platform(libs.firebase.bom)) implementation(libs.firebase.analytics) implementation(libs.firebase.crashlytics) implementation(libs.play.services.ads) diff --git a/build-logic/src/main/java/com/yapp/convention/TestAndroid.kt b/build-logic/src/main/java/com/yapp/convention/TestAndroid.kt index 55bb6d48..4e6afa62 100644 --- a/build-logic/src/main/java/com/yapp/convention/TestAndroid.kt +++ b/build-logic/src/main/java/com/yapp/convention/TestAndroid.kt @@ -3,9 +3,9 @@ package com.yapp.convention import org.gradle.api.Project import org.gradle.kotlin.dsl.dependencies - internal fun Project.configureTestAndroid() { - // feature 모듈에만 테스트 관련 설정 적용 + configureJUnitAndroid() + // feature 모듈에만 UI 테스트 관련 설정 적용 if (path.startsWith(":feature:")) { configureComposeUiTest() } @@ -14,13 +14,21 @@ internal fun Project.configureTestAndroid() { internal fun Project.configureComposeUiTest() { val libs = extensions.libs dependencies { - // Jetpack Compose UI 테스트용 "androidTestImplementation"(libs.findLibrary("compose-ui-test-junit4").get()) - // 테스트용 AndroidManifest 제공해주는 거 (debug 빌드에서만 사용, 테스트 시 Activity 실행 지원) "debugImplementation"(libs.findLibrary("compose-ui-test-manifest").get()) - // 테스트를 실제로 돌려주는 실행기 - "androidTestImplementation"(libs.findLibrary("androidx-test-runner").get()) - // JUnit4 기능을 안드로이드 테스트에 연결해주는 어댑터 - "androidTestImplementation"(libs.findLibrary("androidx-test-ext-junit").get()) + } +} + +@Suppress("UnstableApiUsage") +internal fun Project.configureJUnitAndroid() { + androidExtension.apply { + testOptions { unitTests.all { it.useJUnitPlatform() } } + defaultConfig { testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } + + val libs = extensions.libs + dependencies { + "androidTestImplementation"(libs.findLibrary("androidx-test-ext-junit").get()) + "androidTestImplementation"(libs.findLibrary("androidx-test-runner").get()) + } } } diff --git a/build-logic/src/main/java/orbit.android.feature.gradle.kts b/build-logic/src/main/java/orbit.android.feature.gradle.kts index 9fa57170..aa5803c4 100644 --- a/build-logic/src/main/java/orbit.android.feature.gradle.kts +++ b/build-logic/src/main/java/orbit.android.feature.gradle.kts @@ -6,12 +6,6 @@ plugins { id("orbit.android.compose") } -android { - defaultConfig { - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - } -} - configureHiltAndroid() dependencies { diff --git a/build.gradle.kts b/build.gradle.kts index 46e42947..3f1d3f7f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,12 +7,12 @@ plugins { alias(libs.plugins.kotlin.jvm) apply false alias(libs.plugins.kotlin.serialization) apply false alias(libs.plugins.ksp) apply false + alias(libs.plugins.room) apply false alias(libs.plugins.hilt) apply false alias(libs.plugins.compose.compiler) apply false alias(libs.plugins.google.service) apply false alias(libs.plugins.firebase.app.distribution) apply false alias(libs.plugins.firebase.crashlytics) apply false -// alias(libs.plugins.sentry) apply false } apply { diff --git a/core/database/.gitignore b/core/database/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/core/database/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/database/build.gradle.kts b/core/database/build.gradle.kts new file mode 100644 index 00000000..5796214b --- /dev/null +++ b/core/database/build.gradle.kts @@ -0,0 +1,26 @@ +import com.yapp.convention.setNamespace + +plugins { + id("orbit.android.library") + id("androidx.room") +} + +android { + setNamespace("core.database") + + sourceSets { getByName("androidTest").assets.srcDir("$projectDir/schemas") } +} + +room { + schemaDirectory("$projectDir/schemas") +} + +dependencies { + implementation(projects.domain) + + ksp(libs.androidx.room.compiler) + implementation(libs.androidx.room.ktx) + implementation(libs.androidx.room.runtime) + + androidTestImplementation(libs.androidx.room.testing) +} diff --git a/core/database/consumer-rules.pro b/core/database/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/core/database/proguard-rules.pro b/core/database/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/core/database/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/core/database/schemas/com.yapp.database.AlarmDatabase/1.json b/core/database/schemas/com.yapp.database.AlarmDatabase/1.json new file mode 100644 index 00000000..f700d6a5 --- /dev/null +++ b/core/database/schemas/com.yapp.database.AlarmDatabase/1.json @@ -0,0 +1,118 @@ +{ + "formatVersion": 1, + "database": { + "version": 1, + "identityHash": "d9643e982a8885da158bcd94c55931ff", + "entities": [ + { + "tableName": "alarm_database", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `isAm` INTEGER NOT NULL, `hour` INTEGER NOT NULL, `minute` INTEGER NOT NULL, `second` INTEGER NOT NULL, `repeatDays` INTEGER NOT NULL, `isHolidayAlarmOff` INTEGER NOT NULL, `isSnoozeEnabled` INTEGER NOT NULL, `snoozeInterval` INTEGER NOT NULL, `snoozeCount` INTEGER NOT NULL, `isVibrationEnabled` INTEGER NOT NULL, `isSoundEnabled` INTEGER NOT NULL, `soundUri` TEXT NOT NULL, `soundVolume` INTEGER NOT NULL, `isAlarmActive` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAm", + "columnName": "isAm", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hour", + "columnName": "hour", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "minute", + "columnName": "minute", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "second", + "columnName": "second", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "repeatDays", + "columnName": "repeatDays", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isHolidayAlarmOff", + "columnName": "isHolidayAlarmOff", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isSnoozeEnabled", + "columnName": "isSnoozeEnabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "snoozeInterval", + "columnName": "snoozeInterval", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "snoozeCount", + "columnName": "snoozeCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isVibrationEnabled", + "columnName": "isVibrationEnabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isSoundEnabled", + "columnName": "isSoundEnabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "soundUri", + "columnName": "soundUri", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "soundVolume", + "columnName": "soundVolume", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAlarmActive", + "columnName": "isAlarmActive", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd9643e982a8885da158bcd94c55931ff')" + ] + } +} diff --git a/core/database/schemas/com.yapp.database.AlarmDatabase/2.json b/core/database/schemas/com.yapp.database.AlarmDatabase/2.json new file mode 100644 index 00000000..6e6e50a1 --- /dev/null +++ b/core/database/schemas/com.yapp.database.AlarmDatabase/2.json @@ -0,0 +1,127 @@ +{ + "formatVersion": 1, + "database": { + "version": 2, + "identityHash": "557f9b1e0c2913a691c2aed7587e243c", + "entities": [ + { + "tableName": "alarm_database", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `isAm` INTEGER NOT NULL, `hour` INTEGER NOT NULL, `minute` INTEGER NOT NULL, `second` INTEGER NOT NULL, `repeatDays` INTEGER NOT NULL, `isHolidayAlarmOff` INTEGER NOT NULL, `isSnoozeEnabled` INTEGER NOT NULL, `snoozeInterval` INTEGER NOT NULL, `snoozeCount` INTEGER NOT NULL, `isVibrationEnabled` INTEGER NOT NULL, `isSoundEnabled` INTEGER NOT NULL, `soundUri` TEXT NOT NULL, `soundVolume` INTEGER NOT NULL, `isAlarmActive` INTEGER NOT NULL, `missionType` INTEGER NOT NULL, `missionCount` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAm", + "columnName": "isAm", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hour", + "columnName": "hour", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "minute", + "columnName": "minute", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "second", + "columnName": "second", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "repeatDays", + "columnName": "repeatDays", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isHolidayAlarmOff", + "columnName": "isHolidayAlarmOff", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isSnoozeEnabled", + "columnName": "isSnoozeEnabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "snoozeInterval", + "columnName": "snoozeInterval", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "snoozeCount", + "columnName": "snoozeCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isVibrationEnabled", + "columnName": "isVibrationEnabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isSoundEnabled", + "columnName": "isSoundEnabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "soundUri", + "columnName": "soundUri", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "soundVolume", + "columnName": "soundVolume", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAlarmActive", + "columnName": "isAlarmActive", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "missionType", + "columnName": "missionType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "missionCount", + "columnName": "missionCount", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + } + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '557f9b1e0c2913a691c2aed7587e243c')" + ] + } +} \ No newline at end of file diff --git a/core/database/src/androidTest/java/com/yapp/database/MigrationTest.kt b/core/database/src/androidTest/java/com/yapp/database/MigrationTest.kt new file mode 100644 index 00000000..8f9a76a1 --- /dev/null +++ b/core/database/src/androidTest/java/com/yapp/database/MigrationTest.kt @@ -0,0 +1,80 @@ +package com.yapp.database + +import androidx.room.testing.MigrationTestHelper +import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.assertEquals +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import java.io.IOException + +@RunWith(AndroidJUnit4::class) +class MigrationTest { + + private val testDbName = "test_alarm_database" + + @get:Rule + val helper: MigrationTestHelper = MigrationTestHelper( + InstrumentationRegistry.getInstrumentation(), + AlarmDatabase::class.java, + emptyList(), + FrameworkSQLiteOpenHelperFactory(), + ) + + @Test + @Throws(IOException::class) + fun `버전1에서_버전2로_마이그레이션시_새_컬럼이_기본값으로_채워짐`() { + helper.createDatabase(testDbName, 1).apply { + execSQL( + """ + INSERT INTO alarm_database ( + id, + isAm, + hour, + minute, + second, + repeatDays, + isHolidayAlarmOff, + isSnoozeEnabled, + snoozeInterval, + snoozeCount, + isVibrationEnabled, + isSoundEnabled, + soundUri, + soundVolume, + isAlarmActive + ) VALUES ( + null, -- id (autoGenerate) + 1, -- isAm = true + 7, -- hour + 30, -- minute + 0, -- second + 0, -- repeatDays + 0, -- isHolidayAlarmOff = false + 1, -- isSnoozeEnabled = true + 5, -- snoozeInterval + 3, -- snoozeCount + 1, -- isVibrationEnabled = true + 1, -- isSoundEnabled = true + 'alarm.mp3', -- soundUri + 70, -- soundVolume + 1 -- isAlarmActive = true + ) + """.trimIndent(), + ) + close() + } + + val db = helper.runMigrationsAndValidate(testDbName, 2, true, DatabaseMigrations.MIGRATION_1_2) + + val cursor = db.query("SELECT missionType, missionCount FROM ${AlarmDatabase.DATABASE_NAME}") + cursor.use { + assertEquals(1, it.count) + it.moveToFirst() + assertEquals("TAP", it.getString(it.getColumnIndexOrThrow("missionType"))) + assertEquals(10, it.getInt(it.getColumnIndexOrThrow("missionCount"))) + } + } +} diff --git a/core/database/src/main/AndroidManifest.xml b/core/database/src/main/AndroidManifest.xml new file mode 100644 index 00000000..e1000761 --- /dev/null +++ b/core/database/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + diff --git a/data/src/main/java/com/yapp/data/local/AlarmDao.kt b/core/database/src/main/java/com/yapp/database/AlarmDao.kt similarity index 98% rename from data/src/main/java/com/yapp/data/local/AlarmDao.kt rename to core/database/src/main/java/com/yapp/database/AlarmDao.kt index 41f0291a..6bb80174 100644 --- a/data/src/main/java/com/yapp/data/local/AlarmDao.kt +++ b/core/database/src/main/java/com/yapp/database/AlarmDao.kt @@ -1,4 +1,4 @@ -package com.yapp.data.local +package com.yapp.database import androidx.room.Dao import androidx.room.Insert diff --git a/data/src/main/java/com/yapp/data/local/AlarmDatabase.kt b/core/database/src/main/java/com/yapp/database/AlarmDatabase.kt similarity index 56% rename from data/src/main/java/com/yapp/data/local/AlarmDatabase.kt rename to core/database/src/main/java/com/yapp/database/AlarmDatabase.kt index 027f62d3..988912e2 100644 --- a/data/src/main/java/com/yapp/data/local/AlarmDatabase.kt +++ b/core/database/src/main/java/com/yapp/database/AlarmDatabase.kt @@ -1,9 +1,11 @@ -package com.yapp.data.local +package com.yapp.database import androidx.room.Database import androidx.room.RoomDatabase +import androidx.room.TypeConverters -@Database(entities = [AlarmEntity::class], version = 1, exportSchema = false) +@Database(entities = [AlarmEntity::class], version = 2, exportSchema = true) +@TypeConverters(MissionTypeConverter::class) abstract class AlarmDatabase : RoomDatabase() { abstract fun alarmDao(): AlarmDao diff --git a/data/src/main/java/com/yapp/data/local/AlarmEntity.kt b/core/database/src/main/java/com/yapp/database/AlarmEntity.kt similarity index 91% rename from data/src/main/java/com/yapp/data/local/AlarmEntity.kt rename to core/database/src/main/java/com/yapp/database/AlarmEntity.kt index 56ce2472..f8bc636e 100644 --- a/data/src/main/java/com/yapp/data/local/AlarmEntity.kt +++ b/core/database/src/main/java/com/yapp/database/AlarmEntity.kt @@ -1,8 +1,9 @@ -package com.yapp.data.local +package com.yapp.database import androidx.room.Entity import androidx.room.PrimaryKey import com.yapp.domain.model.Alarm +import com.yapp.domain.model.MissionType @Entity(tableName = AlarmDatabase.DATABASE_NAME) data class AlarmEntity( @@ -10,7 +11,6 @@ data class AlarmEntity( val id: Long = 0, val isAm: Boolean = true, - val hour: Int = 6, val minute: Int = 0, val second: Int = 0, @@ -31,6 +31,9 @@ data class AlarmEntity( val soundVolume: Int = 70, val isAlarmActive: Boolean = true, + + val missionType: MissionType = MissionType.TAP, + val missionCount: Int = 10, ) fun AlarmEntity.toDomain() = Alarm( diff --git a/core/database/src/main/java/com/yapp/database/DatabaseMigrations.kt b/core/database/src/main/java/com/yapp/database/DatabaseMigrations.kt new file mode 100644 index 00000000..60209b5d --- /dev/null +++ b/core/database/src/main/java/com/yapp/database/DatabaseMigrations.kt @@ -0,0 +1,14 @@ +package com.yapp.database + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +internal object DatabaseMigrations { + + val MIGRATION_1_2 = object : Migration(1, 2) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE ${AlarmDatabase.DATABASE_NAME} ADD COLUMN missionType TEXT NOT NULL DEFAULT 'TAP'") + database.execSQL("ALTER TABLE ${AlarmDatabase.DATABASE_NAME} ADD COLUMN missionCount INTEGER NOT NULL DEFAULT 10") + } + } +} diff --git a/core/database/src/main/java/com/yapp/database/MissionTypeConverter.kt b/core/database/src/main/java/com/yapp/database/MissionTypeConverter.kt new file mode 100644 index 00000000..aeb59503 --- /dev/null +++ b/core/database/src/main/java/com/yapp/database/MissionTypeConverter.kt @@ -0,0 +1,17 @@ +package com.yapp.database + +import androidx.room.TypeConverter +import com.yapp.domain.model.MissionType + +class MissionTypeConverter { + + @TypeConverter + fun fromInt(value: Int): MissionType { + return MissionType.fromInt(value) + } + + @TypeConverter + fun toInt(missionType: MissionType): Int { + return missionType.value + } +} diff --git a/data/src/main/java/com/yapp/data/local/di/DatabaseModule.kt b/core/database/src/main/java/com/yapp/database/di/DatabaseModule.kt similarity index 76% rename from data/src/main/java/com/yapp/data/local/di/DatabaseModule.kt rename to core/database/src/main/java/com/yapp/database/di/DatabaseModule.kt index 80bcd808..7c6339f2 100644 --- a/data/src/main/java/com/yapp/data/local/di/DatabaseModule.kt +++ b/core/database/src/main/java/com/yapp/database/di/DatabaseModule.kt @@ -1,9 +1,10 @@ -package com.yapp.data.local.di +package com.yapp.database.di import android.content.Context import androidx.room.Room -import com.yapp.data.local.AlarmDao -import com.yapp.data.local.AlarmDatabase +import com.yapp.database.AlarmDao +import com.yapp.database.AlarmDatabase +import com.yapp.database.DatabaseMigrations import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -24,7 +25,9 @@ class DatabaseModule { context.applicationContext, AlarmDatabase::class.java, AlarmDatabase.DATABASE_NAME, - ).build() + ) + .addMigrations(DatabaseMigrations.MIGRATION_1_2) + .build() } @Provides diff --git a/core/database/src/test/java/com/yapp/database/ExampleUnitTest.kt b/core/database/src/test/java/com/yapp/database/ExampleUnitTest.kt new file mode 100644 index 00000000..47e4e45c --- /dev/null +++ b/core/database/src/test/java/com/yapp/database/ExampleUnitTest.kt @@ -0,0 +1,16 @@ +package com.yapp.database + +import org.junit.Assert.assertEquals +import org.junit.Test + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/data/src/main/java/com/yapp/data/local/di/MediaModule.kt b/core/media/src/main/java/com/yapp/media/di/MediaModule.kt similarity index 64% rename from data/src/main/java/com/yapp/data/local/di/MediaModule.kt rename to core/media/src/main/java/com/yapp/media/di/MediaModule.kt index 1b9e9168..ff75332b 100644 --- a/data/src/main/java/com/yapp/data/local/di/MediaModule.kt +++ b/core/media/src/main/java/com/yapp/media/di/MediaModule.kt @@ -1,9 +1,8 @@ -package com.yapp.data.local.di +package com.yapp.media.di import android.content.ContentResolver import android.content.Context -import com.yapp.data.local.datasource.ImageLocalDataSource -import com.yapp.data.local.datasource.ImageLocalDataSourceImpl +import com.yapp.media.storage.ImageSaver import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -23,7 +22,9 @@ object MediaModule { @Provides @Singleton - fun provideImageLocalDataSource(contentResolver: ContentResolver): ImageLocalDataSource { - return ImageLocalDataSourceImpl(contentResolver) + fun provideImageSaver( + contentResolver: ContentResolver, + ): ImageSaver { + return ImageSaver(contentResolver) } } diff --git a/data/src/main/java/com/yapp/data/local/datasource/ImageLocalDataSourceImpl.kt b/core/media/src/main/java/com/yapp/media/storage/ImageSaver.kt similarity index 76% rename from data/src/main/java/com/yapp/data/local/datasource/ImageLocalDataSourceImpl.kt rename to core/media/src/main/java/com/yapp/media/storage/ImageSaver.kt index 6b1a324f..0c1d98d8 100644 --- a/data/src/main/java/com/yapp/data/local/datasource/ImageLocalDataSourceImpl.kt +++ b/core/media/src/main/java/com/yapp/media/storage/ImageSaver.kt @@ -1,4 +1,4 @@ -package com.yapp.data.local.datasource +package com.yapp.media.storage import android.content.ContentResolver import android.content.ContentValues @@ -9,11 +9,11 @@ import android.util.Log import java.io.IOException import javax.inject.Inject -class ImageLocalDataSourceImpl @Inject constructor( +class ImageSaver @Inject constructor( private val contentResolver: ContentResolver, -) : ImageLocalDataSource { +) { - override suspend fun saveImage(byteArray: ByteArray, fileName: String): Boolean { + fun saveImage(byteArray: ByteArray, fileName: String): Boolean { return try { val bitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size) @@ -32,10 +32,10 @@ class ImageLocalDataSourceImpl @Inject constructor( true } catch (e: SecurityException) { - Log.e("ImageLocalDataSource", "권한 없음: ${e.message}") + Log.e("ImageSaver", "권한 없음: ${e.message}") false } catch (e: IOException) { - Log.e("ImageLocalDataSource", "파일 저장 실패: ${e.message}") + Log.e("ImageSaver", "파일 저장 실패: ${e.message}") false } } diff --git a/data/src/main/java/com/yapp/data/remote/utils/ApiError.kt b/core/network/src/main/java/com/yapp/network/model/ApiError.kt similarity index 67% rename from data/src/main/java/com/yapp/data/remote/utils/ApiError.kt rename to core/network/src/main/java/com/yapp/network/model/ApiError.kt index 947ef498..6bbcb596 100644 --- a/data/src/main/java/com/yapp/data/remote/utils/ApiError.kt +++ b/core/network/src/main/java/com/yapp/network/model/ApiError.kt @@ -1,4 +1,4 @@ -package com.yapp.data.remote.utils +package com.yapp.network.model data class ApiError( override val message: String, diff --git a/core/network/src/main/java/com/yapp/network/utils/ApiCallUtils.kt b/core/network/src/main/java/com/yapp/network/utils/ApiCallUtils.kt new file mode 100644 index 00000000..e80dc731 --- /dev/null +++ b/core/network/src/main/java/com/yapp/network/utils/ApiCallUtils.kt @@ -0,0 +1,33 @@ +package com.yapp.network.utils + +import com.yapp.network.model.ApiError +import kotlinx.coroutines.CancellationException +import retrofit2.HttpException +import java.io.IOException + +inline fun safeApiCall(action: () -> T): Result { + return try { + Result.success(action()) + } catch (exception: Throwable) { + if (exception is CancellationException) throw exception + + val mappedException = when (exception) { + is HttpException -> mapHttpException(exception) + is IOException -> ApiError("네트워크 오류 발생") + else -> ApiError("알 수 없는 오류 발생") + } + + Result.failure(mappedException) + } +} + +fun mapHttpException(exception: HttpException): ApiError { + return when (exception.code()) { + 400 -> ApiError("잘못된 요청") + 401 -> ApiError("인증이 필요합니다") + 403 -> ApiError("권한이 없습니다") + 404 -> ApiError("요청한 리소스를 찾을 수 없습니다") + in 500..599 -> ApiError("서버 오류") + else -> ApiError("알 수 없는 서버 오류가 발생했습니다.") + } +} diff --git a/data/build.gradle.kts b/data/build.gradle.kts index f2c52f14..354e0002 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -10,20 +10,16 @@ android { } dependencies { - implementation(projects.domain) implementation(projects.core.network) + implementation(projects.core.database) implementation(projects.core.datastore) + + implementation(projects.domain) implementation(projects.core.media) implementation(projects.core.remoteconfig) - ksp(libs.androidx.room.compiler) - implementation(libs.androidx.room.ktx) - implementation(libs.androidx.room.runtime) - implementation(libs.androidx.room.paging) - implementation(libs.kotlinx.serialization.json) implementation(libs.retrofit.core) implementation(libs.retrofit.kotlin.serialization) implementation(libs.okhttp.logging) - implementation(libs.androidx.datastore) } diff --git a/data/src/main/java/com/yapp/data/di/RepositoryModule.kt b/data/src/main/java/com/yapp/data/di/RepositoryModule.kt index e50b2ef6..8e3cc519 100644 --- a/data/src/main/java/com/yapp/data/di/RepositoryModule.kt +++ b/data/src/main/java/com/yapp/data/di/RepositoryModule.kt @@ -2,13 +2,11 @@ package com.yapp.data.di import com.yapp.data.repositoryimpl.AlarmRepositoryImpl import com.yapp.data.repositoryimpl.FortuneRepositoryImpl -import com.yapp.data.repositoryimpl.ImageRepositoryImpl import com.yapp.data.repositoryimpl.RemoteConfigRepositoryImpl import com.yapp.data.repositoryimpl.SignUpRepositoryImpl import com.yapp.data.repositoryimpl.UserInfoRepositoryImpl import com.yapp.domain.repository.AlarmRepository import com.yapp.domain.repository.FortuneRepository -import com.yapp.domain.repository.ImageRepository import com.yapp.domain.repository.RemoteConfigRepository import com.yapp.domain.repository.SignUpRepository import com.yapp.domain.repository.UserInfoRepository @@ -33,12 +31,6 @@ abstract class RepositoryModule { fortuneRepository: FortuneRepositoryImpl, ): FortuneRepository - @Binds - @Singleton - abstract fun bindsImageRepository( - imageRepository: ImageRepositoryImpl, - ): ImageRepository - @Binds @Singleton abstract fun bindsUserInfoRepository( diff --git a/data/src/main/java/com/yapp/data/local/datasource/AlarmLocalDataSource.kt b/data/src/main/java/com/yapp/data/local/datasource/AlarmLocalDataSource.kt index 0579eab5..2ff1a748 100644 --- a/data/src/main/java/com/yapp/data/local/datasource/AlarmLocalDataSource.kt +++ b/data/src/main/java/com/yapp/data/local/datasource/AlarmLocalDataSource.kt @@ -1,6 +1,6 @@ package com.yapp.data.local.datasource -import com.yapp.data.local.AlarmEntity +import com.yapp.database.AlarmEntity import com.yapp.domain.model.Alarm import kotlinx.coroutines.flow.Flow diff --git a/data/src/main/java/com/yapp/data/local/datasource/AlarmLocalDataSourceImpl.kt b/data/src/main/java/com/yapp/data/local/datasource/AlarmLocalDataSourceImpl.kt index 18a86ce5..6c109877 100644 --- a/data/src/main/java/com/yapp/data/local/datasource/AlarmLocalDataSourceImpl.kt +++ b/data/src/main/java/com/yapp/data/local/datasource/AlarmLocalDataSourceImpl.kt @@ -1,8 +1,8 @@ package com.yapp.data.local.datasource -import com.yapp.data.local.AlarmDao -import com.yapp.data.local.AlarmEntity -import com.yapp.data.local.toDomain +import com.yapp.database.AlarmDao +import com.yapp.database.AlarmEntity +import com.yapp.database.toDomain import com.yapp.datastore.UserPreferences import com.yapp.domain.model.Alarm import kotlinx.coroutines.flow.Flow diff --git a/data/src/main/java/com/yapp/data/local/datasource/ImageLocalDataSource.kt b/data/src/main/java/com/yapp/data/local/datasource/ImageLocalDataSource.kt deleted file mode 100644 index 607ee87a..00000000 --- a/data/src/main/java/com/yapp/data/local/datasource/ImageLocalDataSource.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.yapp.data.local.datasource - -interface ImageLocalDataSource { - suspend fun saveImage(byteArray: ByteArray, fileName: String = "fortune_${System.currentTimeMillis()}.png"): Boolean -} diff --git a/data/src/main/java/com/yapp/data/remote/datasource/FortuneDataSourceImpl.kt b/data/src/main/java/com/yapp/data/remote/datasource/FortuneDataSourceImpl.kt index f549500d..d8dc2dd7 100644 --- a/data/src/main/java/com/yapp/data/remote/datasource/FortuneDataSourceImpl.kt +++ b/data/src/main/java/com/yapp/data/remote/datasource/FortuneDataSourceImpl.kt @@ -2,7 +2,7 @@ package com.yapp.data.remote.datasource import com.yapp.data.remote.dto.response.FortuneResponse import com.yapp.data.remote.service.ApiService -import com.yapp.data.remote.utils.safeApiCall +import com.yapp.network.utils.safeApiCall import javax.inject.Inject class FortuneDataSourceImpl @Inject constructor( diff --git a/data/src/main/java/com/yapp/data/remote/datasource/SignUpDataSourceImpl.kt b/data/src/main/java/com/yapp/data/remote/datasource/SignUpDataSourceImpl.kt index 2acfa4ff..d6023c3d 100644 --- a/data/src/main/java/com/yapp/data/remote/datasource/SignUpDataSourceImpl.kt +++ b/data/src/main/java/com/yapp/data/remote/datasource/SignUpDataSourceImpl.kt @@ -3,8 +3,8 @@ package com.yapp.data.remote.datasource import android.util.Log import com.yapp.data.remote.dto.request.SignUpRequest import com.yapp.data.remote.service.ApiService -import com.yapp.data.remote.utils.ApiError -import com.yapp.data.remote.utils.safeApiCall +import com.yapp.network.model.ApiError +import com.yapp.network.utils.safeApiCall import javax.inject.Inject class SignUpDataSourceImpl @Inject constructor( diff --git a/data/src/main/java/com/yapp/data/remote/datasource/UserInfoDataSourceImpl.kt b/data/src/main/java/com/yapp/data/remote/datasource/UserInfoDataSourceImpl.kt index 3c6cb580..d81e9189 100644 --- a/data/src/main/java/com/yapp/data/remote/datasource/UserInfoDataSourceImpl.kt +++ b/data/src/main/java/com/yapp/data/remote/datasource/UserInfoDataSourceImpl.kt @@ -3,7 +3,7 @@ package com.yapp.data.remote.datasource import com.yapp.data.remote.dto.request.UpdateUserInfoRequest import com.yapp.data.remote.dto.response.UserResponse import com.yapp.data.remote.service.ApiService -import com.yapp.data.remote.utils.safeApiCall +import com.yapp.network.utils.safeApiCall import javax.inject.Inject class UserInfoDataSourceImpl @Inject constructor( diff --git a/data/src/main/java/com/yapp/data/remote/utils/ApiCallUtils.kt b/data/src/main/java/com/yapp/data/remote/utils/ApiCallUtils.kt deleted file mode 100644 index b1efd325..00000000 --- a/data/src/main/java/com/yapp/data/remote/utils/ApiCallUtils.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.yapp.data.remote.utils - -import retrofit2.HttpException -import java.io.IOException - -internal inline fun safeApiCall(action: () -> T): Result = - runCatching(action).recoverCatching { exception -> - when (exception) { - is HttpException -> throw mapHttpException(exception) - is IOException -> throw ApiError("네트워크 오류 발생") - else -> throw exception - } - } - -private fun mapHttpException(exception: HttpException): ApiError { - return when (exception.code()) { - 400 -> ApiError("잘못된 요청") - 401 -> ApiError("인증이 필요합니다") - 403 -> ApiError("권한이 없습니다") - 404 -> ApiError("요청한 리소스를 찾을 수 없습니다") - in 500..599 -> ApiError("서버 오류") - else -> ApiError("알 수 없는 서버 오류가 발생했습니다.") - } -} diff --git a/data/src/main/java/com/yapp/data/repositoryimpl/AlarmRepositoryImpl.kt b/data/src/main/java/com/yapp/data/repositoryimpl/AlarmRepositoryImpl.kt index 07efaa22..112938be 100644 --- a/data/src/main/java/com/yapp/data/repositoryimpl/AlarmRepositoryImpl.kt +++ b/data/src/main/java/com/yapp/data/repositoryimpl/AlarmRepositoryImpl.kt @@ -2,7 +2,7 @@ package com.yapp.data.repositoryimpl import android.net.Uri import com.yapp.data.local.datasource.AlarmLocalDataSource -import com.yapp.data.local.toEntity +import com.yapp.database.toEntity import com.yapp.domain.model.Alarm import com.yapp.domain.model.AlarmSound import com.yapp.domain.repository.AlarmRepository diff --git a/data/src/main/java/com/yapp/data/repositoryimpl/ImageRepositoryImpl.kt b/data/src/main/java/com/yapp/data/repositoryimpl/ImageRepositoryImpl.kt deleted file mode 100644 index 09658171..00000000 --- a/data/src/main/java/com/yapp/data/repositoryimpl/ImageRepositoryImpl.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.yapp.data.repositoryimpl - -import com.yapp.data.local.datasource.ImageLocalDataSource -import com.yapp.domain.repository.ImageRepository -import javax.inject.Inject - -class ImageRepositoryImpl @Inject constructor( - private val imageLocalDataSource: ImageLocalDataSource, -) : ImageRepository { - - override suspend fun saveImage(byteArray: ByteArray): Boolean { - return imageLocalDataSource.saveImage(byteArray) - } -} diff --git a/domain/src/main/java/com/yapp/domain/model/MissionType.kt b/domain/src/main/java/com/yapp/domain/model/MissionType.kt index 45388ee6..3146d233 100644 --- a/domain/src/main/java/com/yapp/domain/model/MissionType.kt +++ b/domain/src/main/java/com/yapp/domain/model/MissionType.kt @@ -1,16 +1,21 @@ package com.yapp.domain.model -sealed class MissionType { - data object Shake : MissionType() - data object Click : MissionType() +enum class MissionType(val value: Int) { + TAP(0), + SHAKE(1), + ; companion object { + fun fromInt(value: Int): MissionType { + return MissionType.entries.find { it.value == value } ?: TAP + } + fun fromRemoteValue(value: String): MissionType { return when (value) { - "tap_mission" -> Click - "shake_mission" -> Shake + "tap_mission" -> TAP + "shake_mission" -> SHAKE else -> { - Click + TAP } } } diff --git a/domain/src/main/java/com/yapp/domain/repository/ImageRepository.kt b/domain/src/main/java/com/yapp/domain/repository/ImageRepository.kt deleted file mode 100644 index 9abddf8a..00000000 --- a/domain/src/main/java/com/yapp/domain/repository/ImageRepository.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.yapp.domain.repository - -interface ImageRepository { - suspend fun saveImage(byteArray: ByteArray): Boolean -} diff --git a/feature/fortune/src/main/java/com/yapp/fortune/FortuneViewModel.kt b/feature/fortune/src/main/java/com/yapp/fortune/FortuneViewModel.kt index b0774c04..7759dbd1 100644 --- a/feature/fortune/src/main/java/com/yapp/fortune/FortuneViewModel.kt +++ b/feature/fortune/src/main/java/com/yapp/fortune/FortuneViewModel.kt @@ -5,9 +5,9 @@ import android.util.Log import androidx.annotation.DrawableRes import androidx.lifecycle.viewModelScope import com.yapp.domain.repository.FortuneRepository -import com.yapp.domain.repository.ImageRepository import com.yapp.fortune.page.toFortunePages import com.yapp.media.decoder.ImageUtils +import com.yapp.media.storage.ImageSaver import com.yapp.ui.base.BaseViewModel import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.firstOrNull @@ -23,7 +23,7 @@ import javax.inject.Inject class FortuneViewModel @Inject constructor( private val application: Application, private val fortuneRepository: FortuneRepository, - private val imageRepository: ImageRepository, + private val imageSaver: ImageSaver, ) : BaseViewModel( FortuneContract.State(), ) { @@ -99,7 +99,7 @@ class FortuneViewModel @Inject constructor( val bitmap = ImageUtils.getBitmapFromResource(application, resId) val byteArray = ImageUtils.bitmapToByteArray(bitmap) - val isSuccess = imageRepository.saveImage(byteArray) + val isSuccess = imageSaver.saveImage(byteArray, "fortune_${System.currentTimeMillis()}.png") if (isSuccess) { emitSideEffect( diff --git a/feature/mission/src/main/java/com/yapp/mission/MissionContract.kt b/feature/mission/src/main/java/com/yapp/mission/MissionContract.kt index f1812c49..a2af97ef 100644 --- a/feature/mission/src/main/java/com/yapp/mission/MissionContract.kt +++ b/feature/mission/src/main/java/com/yapp/mission/MissionContract.kt @@ -5,7 +5,7 @@ import com.yapp.domain.model.MissionType sealed class MissionContract { data class State( - val missionType: MissionType = MissionType.Click, + val missionType: MissionType = MissionType.TAP, val isMissionTypeLoading: Boolean = true, val isMissionCompleted: Boolean = false, val shakeCount: Int = 0, diff --git a/feature/mission/src/main/java/com/yapp/mission/MissionScreen.kt b/feature/mission/src/main/java/com/yapp/mission/MissionScreen.kt index cd42cdd1..a958e72e 100644 --- a/feature/mission/src/main/java/com/yapp/mission/MissionScreen.kt +++ b/feature/mission/src/main/java/com/yapp/mission/MissionScreen.kt @@ -148,7 +148,7 @@ fun MissionContent( Spacer(modifier = Modifier.heightForScreenPercentage(0.0665f)) when (state.missionType) { - is MissionType.Shake -> { + MissionType.SHAKE -> { if (state.shakeCount == 0) { MissionShakeInitialImage() } else { @@ -156,7 +156,7 @@ fun MissionContent( } } - is MissionType.Click -> { + MissionType.TAP -> { MissionClickCard(state, eventDispatcher) } } @@ -209,8 +209,8 @@ fun MissionProgressBarSection(state: MissionContract.State) { Spacer(modifier = Modifier.heightForScreenPercentage(0.0246f)) MissionProgressBar( currentProgress = when (state.missionType) { - is MissionType.Shake -> state.shakeCount - is MissionType.Click -> state.clickCount + MissionType.SHAKE -> state.shakeCount + MissionType.TAP -> state.clickCount }, totalProgress = 10, modifier = Modifier @@ -227,8 +227,8 @@ fun MissionProgressBarSection(state: MissionContract.State) { @Composable fun MissionLabel(state: MissionContract.State) { val instruction = - if (state.missionType is MissionType.Shake) "10회를 흔들어 부적을 뒤집어줘" else "10회를 눌러 편지를 열어줘" - val count = if (state.missionType is MissionType.Shake) state.shakeCount else state.clickCount + if (state.missionType == MissionType.SHAKE) "10회를 흔들어 부적을 뒤집어줘" else "10회를 눌러 편지를 열어줘" + val count = if (state.missionType == MissionType.SHAKE) state.shakeCount else state.clickCount Text( text = instruction, @@ -316,8 +316,8 @@ fun ExitDialog( type = "mission_fail", properties = mapOf( AnalyticsEvent.MissionPropertiesKeys.MISSION_TYPE to when (state.missionType) { - is MissionType.Shake -> "shake" - is MissionType.Click -> "click" + MissionType.SHAKE -> "shake" + MissionType.TAP -> "click" }, ), ), @@ -401,7 +401,7 @@ private fun MissionRoutePreview() { stateProvider = { MissionContract.State( isMissionTypeLoading = false, - missionType = MissionType.Shake, + missionType = MissionType.SHAKE, shakeCount = 0, clickCount = 0, showFinalAnimation = false, diff --git a/feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt b/feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt index c99b8f79..85dc3e6d 100644 --- a/feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt +++ b/feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt @@ -72,7 +72,7 @@ class MissionViewModel @Inject constructor( } private fun handleShake() = viewModelScope.launch { - if (currentState.missionType !is MissionType.Shake) return@launch + if (currentState.missionType != MissionType.SHAKE) return@launch val currentCount = currentState.shakeCount if (currentCount < 9) { @@ -92,7 +92,7 @@ class MissionViewModel @Inject constructor( } private fun handleClick() = viewModelScope.launch { - if (currentState.missionType !is MissionType.Click) return@launch + if (currentState.missionType != MissionType.TAP) return@launch val currentCount = currentState.clickCount if (currentCount < 9) { diff --git a/gradle.properties b/gradle.properties index 20e2a015..e0d20494 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,4 +20,5 @@ kotlin.code.style=official # Enables namespacing of each library's R class so that its R class includes only the # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library -android.nonTransitiveRClass=true \ No newline at end of file +android.nonTransitiveRClass=true +android.experimental.androidTest.useUnifiedTestPlatform=false diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1c8ef8ab..855c60e8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -29,7 +29,7 @@ kotlinx-collections = "0.3.7" androidx-app-compat = "1.7.0" androidx-core = "1.15.0" androidx-datastore = "1.1.1" -androidx-room = "2.6.1" +androidx-room = "2.7.2" androidx-lifecycle = "2.8.7" @@ -49,12 +49,7 @@ hilt-navigation-compose = "1.2.0" ## Third Party okhttp = "4.12.0" retrofit = "2.11.0" -retrofit-kotlinx-serialization-json = "1.0.0" coil = "2.4.0" -sentry = "5.0.0" -sentry-android = "8.0.0" -sentry-compose = "8.0.0" -gson = "2.11.0" # Google Libraries Versions google-service = "4.4.2" @@ -82,7 +77,6 @@ process-pheonix = "3.0.0" lottie = "6.1.0" accompanist = "0.37.0" materialAndroid = "1.7.5" -flexible-bottomsheet = "0.1.5" amplitude = "1.20.3" [libraries] @@ -107,6 +101,7 @@ androidx-datastore = { group = "androidx.datastore", name = "datastore-preferenc androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "androidx-room" } androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "androidx-room" } androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "androidx-room" } +androidx-room-testing = { group = "androidx.room", name = "room-testing", version.ref = "androidx-room" } androidx-room-paging = { group = "androidx.room", name = "room-paging", version.ref = "androidx-room" } androidx-annotation = { group = "androidx.annotation", name = "annotation", version.ref = "annotation" } @@ -137,7 +132,6 @@ hilt-android-testing = { group = "com.google.dagger", name = "hilt-android-testi hilt-android-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" } hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hilt-navigation-compose" } - # Orbit orbit-core = { group = "org.orbit-mvi", name = "orbit-core", version.ref = "orbit" } orbit-compose = { group = "org.orbit-mvi", name = "orbit-compose", version.ref = "orbit" } @@ -148,9 +142,6 @@ retrofit-core = { group = "com.squareup.retrofit2", name = "retrofit", version.r retrofit-kotlin-serialization = { module = "com.squareup.retrofit2:converter-kotlinx-serialization", version.ref = "retrofit" } okhttp-bom = { group = "com.squareup.okhttp3", name = "okhttp-bom", version.ref = "okhttp" } okhttp-logging = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" } -flexible-bottomsheet = { group = "com.github.skydoves", name = "flexible-bottomsheet-material3", version.ref = "flexible-bottomsheet" } -#sentry-android = { group = "io.sentry", name = "sentry-android", version.ref = "sentry-android" } -#sentry-compose = { group = "io.sentry", name = "sentry-compose", version.ref = "sentry-compose" } # Google Libraries firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebase-bom" } @@ -194,10 +185,10 @@ kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", versi ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" } android-application = { id = "com.android.application", version.ref = "android-gradle-plugin" } android-library = { id = "com.android.library", version.ref = "android-gradle-plugin" } +room = { id = "androidx.room", version.ref = "androidx-room" } hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } android-test = { id = "com.android.test", version.ref = "android-gradle-plugin" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } google-service = { id = "com.google.gms.google-services", version.ref = "google-service" } firebase-app-distribution = { id = "com.google.firebase.appdistribution", version.ref = "firebase-app-distribution" } firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebase-crashlytics" } -#sentry = { id = "io.sentry.android.gradle", version.ref = "sentry" } diff --git a/project.dot.png b/project.dot.png index 11f78f97..65f883c3 100644 Binary files a/project.dot.png and b/project.dot.png differ diff --git a/settings.gradle.kts b/settings.gradle.kts index e86bd657..984c9fc9 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -46,3 +46,4 @@ include(":feature:splash") include(":feature:webview") include(":core:analytics") include(":core:remoteconfig") +include(":core:database")