diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
new file mode 100644
index 00000000..4be681e4
--- /dev/null
+++ b/.github/workflows/deploy.yml
@@ -0,0 +1,23 @@
+name: deploy
+
+on:
+ workflow_dispatch:
+
+jobs:
+ deploy:
+ name: Deploy
+ runs-on: ubuntu-latest
+ steps:
+ - uses: appleboy/ssh-action@v0.1.10
+ with:
+ host: ${{ secrets.HOST }}
+ username: ${{ secrets.USERNAME }}
+ key: ${{ secrets.KEY }}
+ script: |
+ kill -9 $(lsof -i:8080)
+ cd ~/app/2023_1_WAT_BeJuRyu/backend
+ git pull origin develop_server
+ ./gradlew bootJar
+ cd build/libs
+ nohup java -jar BEJURYU-0.0.1-SNAPSHOT.jar > nohup.out 2> nohup.err < /dev/null &
+
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 00000000..e69de29b
diff --git a/.idea/2023_1_WAT_BeJuRyu.iml b/.idea/2023_1_WAT_BeJuRyu.iml
new file mode 100644
index 00000000..d6ebd480
--- /dev/null
+++ b/.idea/2023_1_WAT_BeJuRyu.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 00000000..639900d1
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 00000000..b940cb47
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 00000000..35eb1ddf
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 00000000..2b498b07
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ "keyToString": {
+ "RunOnceActivity.OpenProjectViewOnStart": "true",
+ "RunOnceActivity.ShowReadmeOnStart": "true",
+ "RunOnceActivity.cidr.known.project.marker": "true",
+ "cidr.known.project.marker": "true",
+ "last_opened_file_path": "/Users/jaino/2023_1_WAT_BeJuRyu"
+ }
+}
+
+
+
+
+ 1680270175212
+
+
+ 1680270175212
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index ff3012cb..00f54804 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,60 @@
+## Android Modules ๐
+
+```
+ ๐android
+ โฃ ๐app
+ โฃ ๐core
+ โ โฃ ๐data
+ โ โฃ ๐domain
+ โ โฃ ๐designsystem
+ โ โฃ ๐model
+ โ โฃ ๐network
+ โ โฃ ๐common
+ โ โ ๐datastore
+ โฃ ๐build-logic
+ โ โ ๐convention
+ โฃ ๐feature
+ โ โฃ ๐auth
+ โ โฃ ๐setting
+ โ โฃ ๐analysis
+ โ โฃ ๐review
+ โ โฃ ๐account
+ โ โฃ ๐home
+ โ โ ๐dictionary
+```
+
+
+## Android Tech Stack
+
+- Minumum SDK 26 & Target SDK 33
+- Kotlin
+ - Kotlin 1.8.10v
+ - Coroutines & Flow
+- Dependency Injection
+ - Dagger Hilt
+- Android Jetpack
+ - CameraX & PhotoPicker
+ - Navigation Component
+ - DataBinding & ViewBinding
+- Local
+ - Encrypted SharedPreference
+- Remote
+ - Retrofit2 & OkHttp3
+ - Kotlinx Serialization
+- Firebase
+ - Analytics & Crashlytics
+- Third Party
+ - Glide
+ - Material Components
+ - Timber
+ - ProcessPhoenix
+ - Lottie
+ - Skydoves ProgressView
+- [verison_catalog](https://github.com/pknu-wap/2023_1_WAT_BeJuRyu/blob/develop_android/android/gradle/libs.version.toml)
+
## ๐ท Contributors ๐ท
diff --git a/android/.gitignore b/android/.gitignore
new file mode 100644
index 00000000..aa724b77
--- /dev/null
+++ b/android/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/android/.idea/.name b/android/.idea/.name
new file mode 100644
index 00000000..a78013c4
--- /dev/null
+++ b/android/.idea/.name
@@ -0,0 +1 @@
+BeJuRyu
\ No newline at end of file
diff --git a/android/.idea/compiler.xml b/android/.idea/compiler.xml
new file mode 100644
index 00000000..fb7f4a8a
--- /dev/null
+++ b/android/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/.idea/gradle.xml b/android/.idea/gradle.xml
new file mode 100644
index 00000000..6bc7dab8
--- /dev/null
+++ b/android/.idea/gradle.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/.idea/misc.xml b/android/.idea/misc.xml
new file mode 100644
index 00000000..52788e54
--- /dev/null
+++ b/android/.idea/misc.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/.idea/vcs.xml b/android/.idea/vcs.xml
new file mode 100644
index 00000000..6c0b8635
--- /dev/null
+++ b/android/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/.gitignore b/android/app/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/android/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
new file mode 100644
index 00000000..268362a5
--- /dev/null
+++ b/android/app/build.gradle.kts
@@ -0,0 +1,76 @@
+import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties
+
+plugins {
+ id("com.jaino.application")
+ id("com.jaino.hilt")
+ alias(libs.plugins.google.services)
+ alias(libs.plugins.google.firebase.crashlytics)
+}
+
+android {
+
+ namespace = "com.jaino.app"
+
+ defaultConfig {
+ val libs = extensions.getByType().named("libs")
+ versionName = libs.findVersion("appVersion").get().requiredVersion
+ versionCode = checkNotNull(libs.findVersion("versionCode").get().requiredVersion).toInt()
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+
+ buildConfigField("String", "KAKAO_NATIVE_APP_KEY", getLocalProperty("KAKAO_NATIVE_APP_KEY"))
+ resValue("string", "KAKAO_NATIVE_APP_KEY", "kakao${getLocalProperty("KAKAO_NATIVE_APP_KEY")}")
+
+ }
+
+ buildTypes {
+ getByName("release") {
+ isMinifyEnabled = true
+ proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
+ }
+ }
+}
+
+fun getLocalProperty(property: String): String {
+ return gradleLocalProperties(rootDir).getProperty(property)
+}
+
+dependencies {
+
+ implementation(project(":feature:auth"))
+ implementation(project(":feature:setting"))
+ implementation(project(":feature:analysis"))
+ implementation(project(":feature:dictionary"))
+ implementation(project(":feature:review"))
+ implementation(project(":feature:account"))
+ implementation(project(":feature:home"))
+ implementation(project(":core:common"))
+ implementation(project(":core:designsystem"))
+ implementation(libs.bundles.kotlin)
+ implementation(libs.bundles.androidx)
+ implementation(libs.androidx.splash)
+ implementation(libs.hilt)
+ kapt(libs.hilt.kapt)
+ implementation(libs.androidx.hilt.work)
+ kapt(libs.androidx.hilt.work.compiler)
+ implementation(libs.bundles.navigation)
+
+ // Third-Party
+ implementation(libs.kakao.login)
+ implementation(libs.kakao.share)
+ implementation(libs.timber)
+ implementation(libs.material)
+ implementation(platform(libs.firebase))
+ implementation(libs.bundles.firebase)
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.androidx.test.junit)
+ androidTestImplementation(libs.androidx.test.espresso)
+}
+
+kapt {
+ correctErrorTypes = true
+}
+
+hilt {
+ enableAggregatingTask = true
+}
diff --git a/android/app/google-services.json b/android/app/google-services.json
new file mode 100644
index 00000000..12936589
--- /dev/null
+++ b/android/app/google-services.json
@@ -0,0 +1,47 @@
+{
+ "project_info": {
+ "project_number": "253577652264",
+ "project_id": "bejuryu-a435e",
+ "storage_bucket": "bejuryu-a435e.appspot.com"
+ },
+ "client": [
+ {
+ "client_info": {
+ "mobilesdk_app_id": "1:253577652264:android:41cef8bf061bf16f6aed1d",
+ "android_client_info": {
+ "package_name": "com.jaino.app"
+ }
+ },
+ "oauth_client": [
+ {
+ "client_id": "253577652264-qncdl8hta12rkpr47kdss9pp16skvs7t.apps.googleusercontent.com",
+ "client_type": 1,
+ "android_info": {
+ "package_name": "com.jaino.app",
+ "certificate_hash": "bd20aa69d65c307c64c94600984ca19d869f40d0"
+ }
+ },
+ {
+ "client_id": "253577652264-89nk80hviqpnrme88if59gbh0t8mp90a.apps.googleusercontent.com",
+ "client_type": 3
+ }
+ ],
+ "api_key": [
+ {
+ "current_key": "AIzaSyA--hrhASQUUwHrHFr_aJXwEr0_klQiPqQ"
+ }
+ ],
+ "services": {
+ "appinvite_service": {
+ "other_platform_oauth_client": [
+ {
+ "client_id": "253577652264-89nk80hviqpnrme88if59gbh0t8mp90a.apps.googleusercontent.com",
+ "client_type": 3
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "configuration_version": "1"
+}
\ No newline at end of file
diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro
new file mode 100644
index 00000000..ff59496d
--- /dev/null
+++ b/android/app/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.kts.
+#
+# 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/android/app/src/androidTest/java/com/jaino/app/ExampleInstrumentedTest.kt b/android/app/src/androidTest/java/com/jaino/app/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..310f639f
--- /dev/null
+++ b/android/app/src/androidTest/java/com/jaino/app/ExampleInstrumentedTest.kt
@@ -0,0 +1,25 @@
+package com.jaino.app
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class
+ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.jaino.BeJuRyu", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..f0a25434
--- /dev/null
+++ b/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/icon-playstore.png b/android/app/src/main/icon-playstore.png
new file mode 100644
index 00000000..9641f7b1
Binary files /dev/null and b/android/app/src/main/icon-playstore.png differ
diff --git a/android/app/src/main/java/com/jaino/app/BeJuRyuApplication.kt b/android/app/src/main/java/com/jaino/app/BeJuRyuApplication.kt
new file mode 100644
index 00000000..27cb924f
--- /dev/null
+++ b/android/app/src/main/java/com/jaino/app/BeJuRyuApplication.kt
@@ -0,0 +1,17 @@
+package com.jaino.app
+
+import android.app.Application
+import androidx.appcompat.app.AppCompatDelegate
+import com.kakao.sdk.common.KakaoSdk
+import dagger.hilt.android.HiltAndroidApp
+import timber.log.Timber
+
+@HiltAndroidApp
+class BeJuRyuApplication : Application(){
+ override fun onCreate() {
+ super.onCreate()
+ Timber.plant(Timber.DebugTree())
+ KakaoSdk.init(this, BuildConfig.KAKAO_NATIVE_APP_KEY)
+ AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/main/java/com/jaino/app/HomeActivity.kt b/android/app/src/main/java/com/jaino/app/HomeActivity.kt
new file mode 100644
index 00000000..97021fe1
--- /dev/null
+++ b/android/app/src/main/java/com/jaino/app/HomeActivity.kt
@@ -0,0 +1,43 @@
+package com.jaino.app
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.net.toUri
+import androidx.navigation.NavController
+import androidx.navigation.fragment.NavHostFragment
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class HomeActivity : AppCompatActivity() {
+
+ private lateinit var navController: NavController
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_home)
+ initFragmentContainer()
+ navigateToDestination()
+ }
+
+ private fun initFragmentContainer(){
+ val navHostFragment = supportFragmentManager
+ .findFragmentById(R.id.home_nav_host_fragment) as NavHostFragment
+ navController = navHostFragment.navController
+ }
+ private fun navigateToDestination(){
+ val destination = intent.getStringExtra("DESTINATION") ?: return
+ if(destination.isNotEmpty()){
+ navController.navigate(destination.toUri())
+ }
+ }
+
+ companion object {
+ fun getIntent(context: Context, destination: String): Intent {
+ return Intent(context, HomeActivity::class.java).apply {
+ putExtra("DESTINATION", destination)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/main/java/com/jaino/app/di/AppModule.kt b/android/app/src/main/java/com/jaino/app/di/AppModule.kt
new file mode 100644
index 00000000..b35ac471
--- /dev/null
+++ b/android/app/src/main/java/com/jaino/app/di/AppModule.kt
@@ -0,0 +1,30 @@
+package com.jaino.app.di
+
+import android.app.Application
+import com.jaino.app.navigator.AppNavigatorImpl
+import com.jaino.common.navigation.AppNavigator
+import com.kakao.sdk.user.UserApiClient
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+object AppModule {
+
+ @Provides
+ @Singleton
+ @ApplicationContext
+ fun provideApplication(application: Application) = application
+
+ @Provides
+ @Singleton
+ fun provideKakaoClient(): UserApiClient = UserApiClient.instance
+
+ @Provides
+ @Singleton
+ fun provideAppNavigator(appNavigator: AppNavigatorImpl): AppNavigator = appNavigator
+}
\ No newline at end of file
diff --git a/android/app/src/main/java/com/jaino/app/navigator/AppNavigatorImpl.kt b/android/app/src/main/java/com/jaino/app/navigator/AppNavigatorImpl.kt
new file mode 100644
index 00000000..f6a01dac
--- /dev/null
+++ b/android/app/src/main/java/com/jaino/app/navigator/AppNavigatorImpl.kt
@@ -0,0 +1,22 @@
+package com.jaino.app.navigator
+
+import android.content.Context
+import android.content.Intent
+import com.jaino.account.AccountActivity
+import com.jaino.app.HomeActivity
+import com.jaino.auth.AuthActivity
+import com.jaino.common.navigation.AppNavigator
+import dagger.hilt.android.qualifiers.ApplicationContext
+import javax.inject.Inject
+
+class AppNavigatorImpl @Inject constructor(
+ @ApplicationContext private val context: Context
+): AppNavigator {
+ override fun navigateToAuth(): Intent = AuthActivity.getIntent(context)
+
+ override fun navigateToAccount(): Intent = AccountActivity.getIntent(context)
+
+ override fun navigateToHome(destination: String): Intent =
+ HomeActivity.getIntent(context, destination)
+
+}
\ No newline at end of file
diff --git a/android/app/src/main/res/layout/activity_home.xml b/android/app/src/main/res/layout/activity_home.xml
new file mode 100644
index 00000000..0e67a867
--- /dev/null
+++ b/android/app/src/main/res/layout/activity_home.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/mipmap-anydpi-v26/icon.xml b/android/app/src/main/res/mipmap-anydpi-v26/icon.xml
new file mode 100644
index 00000000..c41948fa
--- /dev/null
+++ b/android/app/src/main/res/mipmap-anydpi-v26/icon.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/mipmap-anydpi-v26/icon_round.xml b/android/app/src/main/res/mipmap-anydpi-v26/icon_round.xml
new file mode 100644
index 00000000..c41948fa
--- /dev/null
+++ b/android/app/src/main/res/mipmap-anydpi-v26/icon_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/mipmap-hdpi/icon.png b/android/app/src/main/res/mipmap-hdpi/icon.png
new file mode 100644
index 00000000..f0402245
Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/icon.png differ
diff --git a/android/app/src/main/res/mipmap-hdpi/icon_foreground.png b/android/app/src/main/res/mipmap-hdpi/icon_foreground.png
new file mode 100644
index 00000000..24387907
Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/icon_foreground.png differ
diff --git a/android/app/src/main/res/mipmap-hdpi/icon_round.png b/android/app/src/main/res/mipmap-hdpi/icon_round.png
new file mode 100644
index 00000000..e10cb04d
Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/icon_round.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/icon.png b/android/app/src/main/res/mipmap-mdpi/icon.png
new file mode 100644
index 00000000..db129ad5
Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/icon.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/icon_foreground.png b/android/app/src/main/res/mipmap-mdpi/icon_foreground.png
new file mode 100644
index 00000000..e2382ab7
Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/icon_foreground.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/icon_round.png b/android/app/src/main/res/mipmap-mdpi/icon_round.png
new file mode 100644
index 00000000..aa063f2a
Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/icon_round.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/icon.png b/android/app/src/main/res/mipmap-xhdpi/icon.png
new file mode 100644
index 00000000..8897821f
Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/icon.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/icon_foreground.png b/android/app/src/main/res/mipmap-xhdpi/icon_foreground.png
new file mode 100644
index 00000000..3fdb640c
Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/icon_foreground.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/icon_round.png b/android/app/src/main/res/mipmap-xhdpi/icon_round.png
new file mode 100644
index 00000000..e020c9bd
Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/icon_round.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/icon.png b/android/app/src/main/res/mipmap-xxhdpi/icon.png
new file mode 100644
index 00000000..661cbffb
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/icon.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/icon_foreground.png b/android/app/src/main/res/mipmap-xxhdpi/icon_foreground.png
new file mode 100644
index 00000000..11c0a42a
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/icon_foreground.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/icon_round.png b/android/app/src/main/res/mipmap-xxhdpi/icon_round.png
new file mode 100644
index 00000000..686f4f91
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/icon_round.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/icon.png b/android/app/src/main/res/mipmap-xxxhdpi/icon.png
new file mode 100644
index 00000000..811baad6
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/icon.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/icon_foreground.png b/android/app/src/main/res/mipmap-xxxhdpi/icon_foreground.png
new file mode 100644
index 00000000..e909d4e7
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/icon_foreground.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/icon_round.png b/android/app/src/main/res/mipmap-xxxhdpi/icon_round.png
new file mode 100644
index 00000000..9ac3854e
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/icon_round.png differ
diff --git a/android/app/src/main/res/navigation/app_nav.xml b/android/app/src/main/res/navigation/app_nav.xml
new file mode 100644
index 00000000..1ff0747a
--- /dev/null
+++ b/android/app/src/main/res/navigation/app_nav.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/values/icon_background.xml b/android/app/src/main/res/values/icon_background.xml
new file mode 100644
index 00000000..f213bd64
--- /dev/null
+++ b/android/app/src/main/res/values/icon_background.xml
@@ -0,0 +1,4 @@
+
+
+ #FFFFFF
+
\ No newline at end of file
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
new file mode 100644
index 00000000..a09cede9
--- /dev/null
+++ b/android/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ BE์ฃผ๋ฅ
+
\ No newline at end of file
diff --git a/android/app/src/main/res/values/themes.xml b/android/app/src/main/res/values/themes.xml
new file mode 100644
index 00000000..0c02a69e
--- /dev/null
+++ b/android/app/src/main/res/values/themes.xml
@@ -0,0 +1,11 @@
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/xml/backup_rules.xml b/android/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 00000000..fa0f996d
--- /dev/null
+++ b/android/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/xml/data_extraction_rules.xml b/android/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 00000000..9ee9997b
--- /dev/null
+++ b/android/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/test/java/com/jaino/app/ExampleUnitTest.kt b/android/app/src/test/java/com/jaino/app/ExampleUnitTest.kt
new file mode 100644
index 00000000..0c5e89ed
--- /dev/null
+++ b/android/app/src/test/java/com/jaino/app/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.jaino.app
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * 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)
+ }
+}
\ No newline at end of file
diff --git a/android/build-logic/convention/.gitignore b/android/build-logic/convention/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/android/build-logic/convention/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/android/build-logic/convention/build.gradle.kts b/android/build-logic/convention/build.gradle.kts
new file mode 100644
index 00000000..8b553d63
--- /dev/null
+++ b/android/build-logic/convention/build.gradle.kts
@@ -0,0 +1,38 @@
+plugins {
+ `kotlin-dsl`
+}
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+}
+
+dependencies {
+ compileOnly(libs.android.build)
+ compileOnly(libs.kotlin.gradle)
+}
+
+gradlePlugin {
+ plugins {
+ create("android-application") {
+ id = "com.jaino.application"
+ implementationClass = "com.jaino.convention.AndroidApplicationPlugin"
+ }
+ create("android-feature") {
+ id = "com.jaino.feature"
+ implementationClass = "com.jaino.convention.AndroidFeaturePlugin"
+ }
+ create("android-kotlin") {
+ id = "com.jaino.kotlin"
+ implementationClass = "com.jaino.convention.AndroidKotlinPlugin"
+ }
+ create("android-hilt") {
+ id = "com.jaino.hilt"
+ implementationClass = "com.jaino.convention.AndroidHiltPlugin"
+ }
+ create("kotlin-serialization") {
+ id = "com.jaino.serialization"
+ implementationClass = "com.jaino.convention.KotlinSerializationPlugin"
+ }
+ }
+}
diff --git a/android/build-logic/convention/src/androidTest/java/com/jaino/convention/ExampleInstrumentedTest.kt b/android/build-logic/convention/src/androidTest/java/com/jaino/convention/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..9368c838
--- /dev/null
+++ b/android/build-logic/convention/src/androidTest/java/com/jaino/convention/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.jaino.convention
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.jaino.convention.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/android/build-logic/convention/src/main/AndroidManifest.xml b/android/build-logic/convention/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a5918e68
--- /dev/null
+++ b/android/build-logic/convention/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/android/build-logic/convention/src/main/java/com/jaino/convention/AndroidApplicationPlugin.kt b/android/build-logic/convention/src/main/java/com/jaino/convention/AndroidApplicationPlugin.kt
new file mode 100644
index 00000000..69209d29
--- /dev/null
+++ b/android/build-logic/convention/src/main/java/com/jaino/convention/AndroidApplicationPlugin.kt
@@ -0,0 +1,11 @@
+package com.jaino.convention
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+
+class AndroidApplicationPlugin : Plugin {
+ override fun apply(target: Project) = with(target) {
+ plugins.apply("com.android.application")
+ configureAndroidCommonPlugin()
+ }
+}
diff --git a/android/build-logic/convention/src/main/java/com/jaino/convention/AndroidFeaturePlugin.kt b/android/build-logic/convention/src/main/java/com/jaino/convention/AndroidFeaturePlugin.kt
new file mode 100644
index 00000000..b6ea29b2
--- /dev/null
+++ b/android/build-logic/convention/src/main/java/com/jaino/convention/AndroidFeaturePlugin.kt
@@ -0,0 +1,11 @@
+package com.jaino.convention
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+
+class AndroidFeaturePlugin : Plugin {
+ override fun apply(target: Project) = with(target) {
+ plugins.apply("com.android.library")
+ configureAndroidCommonPlugin()
+ }
+}
diff --git a/android/build-logic/convention/src/main/java/com/jaino/convention/AndroidHiltPlugin.kt b/android/build-logic/convention/src/main/java/com/jaino/convention/AndroidHiltPlugin.kt
new file mode 100644
index 00000000..db3c649f
--- /dev/null
+++ b/android/build-logic/convention/src/main/java/com/jaino/convention/AndroidHiltPlugin.kt
@@ -0,0 +1,21 @@
+package com.jaino.convention
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.artifacts.VersionCatalogsExtension
+import org.gradle.kotlin.dsl.dependencies
+import org.gradle.kotlin.dsl.getByType
+
+class AndroidHiltPlugin : Plugin {
+ override fun apply(target: Project) = with(target) {
+ with(plugins) {
+ apply("com.google.dagger.hilt.android")
+ }
+
+ val libs = extensions.getByType().named("libs")
+ dependencies {
+ "implementation"(libs.findLibrary("hilt").get())
+ "kapt"(libs.findLibrary("hilt.kapt").get())
+ }
+ }
+}
diff --git a/android/build-logic/convention/src/main/java/com/jaino/convention/AndroidKotlinPlugin.kt b/android/build-logic/convention/src/main/java/com/jaino/convention/AndroidKotlinPlugin.kt
new file mode 100644
index 00000000..43319188
--- /dev/null
+++ b/android/build-logic/convention/src/main/java/com/jaino/convention/AndroidKotlinPlugin.kt
@@ -0,0 +1,47 @@
+package com.jaino.convention
+
+import com.android.build.gradle.BaseExtension
+import org.gradle.api.JavaVersion
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.artifacts.VersionCatalogsExtension
+import org.gradle.api.plugins.ExtensionAware
+import org.gradle.kotlin.dsl.configure
+import org.gradle.kotlin.dsl.dependencies
+import org.gradle.kotlin.dsl.getByType
+import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions
+
+class AndroidKotlinPlugin : Plugin {
+ override fun apply(target: Project) = with(target) {
+ with(plugins) {
+ apply("kotlin-android")
+ }
+
+ val libs = extensions.getByType().named("libs")
+ extensions.getByType().apply {
+ setCompileSdkVersion(libs.findVersion("compileSdk").get().requiredVersion.toInt())
+
+ defaultConfig {
+ minSdk = libs.findVersion("minSdk").get().requiredVersion.toInt()
+ targetSdk = libs.findVersion("targetSdk").get().requiredVersion.toInt()
+ }
+
+ compileOptions {
+ isCoreLibraryDesugaringEnabled = true
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+
+ (this as ExtensionAware).configure {
+ jvmTarget = "11"
+ }
+ }
+
+ dependencies {
+ "coreLibraryDesugaring"(libs.findLibrary("desugarLibs").get())
+ "implementation"(libs.findLibrary("kotlin").get())
+ "implementation"(libs.findLibrary("kotlin.coroutines").get())
+ "implementation"(libs.findLibrary("kotlin.datetime").get())
+ }
+ }
+}
diff --git a/android/build-logic/convention/src/main/java/com/jaino/convention/CommonConfigs.kt b/android/build-logic/convention/src/main/java/com/jaino/convention/CommonConfigs.kt
new file mode 100644
index 00000000..1ad1fcf1
--- /dev/null
+++ b/android/build-logic/convention/src/main/java/com/jaino/convention/CommonConfigs.kt
@@ -0,0 +1,33 @@
+package com.jaino.convention
+
+import com.android.build.gradle.BaseExtension
+import org.gradle.api.Project
+import org.gradle.api.artifacts.VersionCatalogsExtension
+import org.gradle.kotlin.dsl.apply
+import org.gradle.kotlin.dsl.dependencies
+import org.gradle.kotlin.dsl.getByType
+
+internal fun Project.configureAndroidCommonPlugin() {
+ apply()
+ apply()
+ with(plugins) {
+ apply("kotlin-kapt")
+ apply("kotlin-parcelize")
+ }
+ apply()
+
+ extensions.getByType().apply {
+ buildFeatures.apply {
+ dataBinding.enable = true
+ viewBinding = true
+ }
+ }
+
+ val libs = extensions.getByType().named("libs")
+ dependencies {
+ "implementation"(libs.findLibrary("androidx.core").get())
+ "implementation"(libs.findLibrary("androidx.appcompat").get())
+ "implementation"(libs.findLibrary("androidx.lifecycle-viewmodel").get())
+ "implementation"(libs.findLibrary("material").get())
+ }
+}
diff --git a/android/build-logic/convention/src/main/java/com/jaino/convention/KotlinSerializationPlugin.kt b/android/build-logic/convention/src/main/java/com/jaino/convention/KotlinSerializationPlugin.kt
new file mode 100644
index 00000000..e6108c6f
--- /dev/null
+++ b/android/build-logic/convention/src/main/java/com/jaino/convention/KotlinSerializationPlugin.kt
@@ -0,0 +1,20 @@
+package com.jaino.convention
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.artifacts.VersionCatalogsExtension
+import org.gradle.kotlin.dsl.dependencies
+import org.gradle.kotlin.dsl.getByType
+
+class KotlinSerializationPlugin : Plugin {
+ override fun apply(target: Project) = with(target) {
+ with(plugins) {
+ apply("org.jetbrains.kotlin.plugin.serialization")
+ }
+
+ val libs = extensions.getByType().named("libs")
+ dependencies {
+ "implementation"(libs.findLibrary("kotlin.serialization.json").get())
+ }
+ }
+}
diff --git a/android/build-logic/convention/src/test/java/com/jaino/convention/ExampleUnitTest.kt b/android/build-logic/convention/src/test/java/com/jaino/convention/ExampleUnitTest.kt
new file mode 100644
index 00000000..9de94759
--- /dev/null
+++ b/android/build-logic/convention/src/test/java/com/jaino/convention/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.jaino.convention
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * 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)
+ }
+}
\ No newline at end of file
diff --git a/android/build-logic/gradle.properties b/android/build-logic/gradle.properties
new file mode 100644
index 00000000..1c9073eb
--- /dev/null
+++ b/android/build-logic/gradle.properties
@@ -0,0 +1,4 @@
+# Gradle properties are not passed to included builds https://github.com/gradle/gradle/issues/2534
+org.gradle.parallel=true
+org.gradle.caching=true
+org.gradle.configureondemand=true
diff --git a/android/build-logic/gradle/wrapper/gradle-wrapper.properties b/android/build-logic/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..2bd1c42d
--- /dev/null
+++ b/android/build-logic/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Sat Apr 01 02:49:49 KST 2023
+distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
+distributionPath=wrapper/dists
+zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
diff --git a/android/build-logic/settings.gradle.kts b/android/build-logic/settings.gradle.kts
new file mode 100644
index 00000000..579273dc
--- /dev/null
+++ b/android/build-logic/settings.gradle.kts
@@ -0,0 +1,14 @@
+dependencyResolutionManagement {
+ repositories {
+ google()
+ mavenCentral()
+ }
+ versionCatalogs {
+ create("libs") {
+ from(files("../gradle/libs.version.toml"))
+ }
+ }
+}
+
+rootProject.name = "build-logic"
+include(":convention")
diff --git a/android/build.gradle.kts b/android/build.gradle.kts
new file mode 100644
index 00000000..a21d8e44
--- /dev/null
+++ b/android/build.gradle.kts
@@ -0,0 +1,28 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+ repositories {
+ google()
+ mavenCentral()
+ }
+ dependencies {
+ classpath(libs.android.build)
+ classpath(libs.kotlin.gradle)
+ classpath(libs.hilt.gradle)
+ classpath(libs.nav.safeargs)
+ }
+}
+
+plugins {
+ alias(libs.plugins.android.application) apply false
+ alias(libs.plugins.android.library) apply false
+ alias(libs.plugins.kotlin.android) apply false
+ alias(libs.plugins.kotlin.kapt) apply false
+ alias(libs.plugins.dagger.hilt) apply false
+ alias(libs.plugins.google.firebase.crashlytics) apply false
+ alias(libs.plugins.kotlinx.serialization) apply false
+ alias(libs.plugins.androidx.navigation.safeargs) apply false
+}
+
+tasks.register("clean", Delete::class) {
+ delete(rootProject.buildDir)
+}
\ No newline at end of file
diff --git a/android/core/common/.gitignore b/android/core/common/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/android/core/common/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/android/core/common/build.gradle.kts b/android/core/common/build.gradle.kts
new file mode 100644
index 00000000..09d22d6d
--- /dev/null
+++ b/android/core/common/build.gradle.kts
@@ -0,0 +1,20 @@
+plugins {
+ id("com.jaino.feature")
+ id("com.jaino.serialization")
+}
+
+android {
+ namespace = "com.jaino.common"
+}
+
+dependencies {
+ implementation(project(":core:designsystem"))
+ implementation(libs.kotlin.datetime)
+ implementation(libs.timber)
+ implementation(libs.glide)
+ implementation(libs.androidx.appcompat)
+ implementation(libs.material)
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.androidx.test.junit)
+ androidTestImplementation(libs.androidx.test.espresso)
+}
\ No newline at end of file
diff --git a/android/core/common/consumer-rules.pro b/android/core/common/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/android/core/common/proguard-rules.pro b/android/core/common/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/android/core/common/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/android/core/common/src/androidTest/java/com/jaino/common/ExampleInstrumentedTest.kt b/android/core/common/src/androidTest/java/com/jaino/common/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..961ec9fe
--- /dev/null
+++ b/android/core/common/src/androidTest/java/com/jaino/common/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.jaino.common
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.jaino.navigation.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/android/core/common/src/main/AndroidManifest.xml b/android/core/common/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a5918e68
--- /dev/null
+++ b/android/core/common/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/android/core/common/src/main/java/com/jaino/common/constant/SentimentConst.kt b/android/core/common/src/main/java/com/jaino/common/constant/SentimentConst.kt
new file mode 100644
index 00000000..1d79a679
--- /dev/null
+++ b/android/core/common/src/main/java/com/jaino/common/constant/SentimentConst.kt
@@ -0,0 +1,8 @@
+package com.jaino.common.constant
+
+const val SAD = "์ฌํ"
+const val HAPPY = "๊ธฐ์จ"
+const val MEDIAN = "ํ์จ"
+const val SAD_CONTENT = "ํ๋ค ํ๋ฃจ์ ์ฌํ์ ๋๋ผ๊ณ ์๋ ๋น์ ์๊ฒ,\n์ ํ ์ํ๋ฉฐ, ์ฌํ์ด ์๋ค์ด๊ฐ ๋ง์์ ๋ค์ ํ๊ธฐ์ฐจ๊ฒ ๋ง๋ค์ด ๋ณด๋ ๊ฒ์ ์ด๋จ๊น์?"
+const val HAPPY_CONTENT = "์ผ์ ์์์ ํ๋ณต์ ๋ง๋ฝํ๊ณ ์๋ ๋น์ ์๊ฒ, \n์ ํ ์ํ๋ฉฐ, ์ง๊ธ ์ด ์๊ฐ์ ์ฆ๊ฑฐ์์ ๋ํด๋ณด๋ ๊ฒ์ ์ด๋จ๊น์?"
+const val MEDIAN_CONTENT = "์ผ์์์ ์กฐํ์ ์์ ์ ๋๋ผ๊ณ ์๋ ๋น์ ์๊ฒ,\n ์ ํ ์ํ๋ฉฐ, ๊ฐ๋์ ํ์ด๋ด๊ณ ์ฆ๊ฑฐ์์ ๋ํด๋ณด๋ ๊ฒ์ ์ด๋จ๊น์?"
\ No newline at end of file
diff --git a/android/core/common/src/main/java/com/jaino/common/extensions/ContentValueExt.kt b/android/core/common/src/main/java/com/jaino/common/extensions/ContentValueExt.kt
new file mode 100644
index 00000000..a900bbfb
--- /dev/null
+++ b/android/core/common/src/main/java/com/jaino/common/extensions/ContentValueExt.kt
@@ -0,0 +1,20 @@
+package com.jaino.common.extensions
+
+import android.content.ContentValues
+import android.os.Build
+import android.provider.MediaStore
+import java.text.SimpleDateFormat
+import java.util.*
+
+fun ContentValues.getCurrentFileName() : ContentValues {
+ val name = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault())
+ .format(System.currentTimeMillis())
+
+ return ContentValues().apply {
+ put(MediaStore.MediaColumns.DISPLAY_NAME, name)
+ put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
+ if(Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
+ put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/HealthC")
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/core/common/src/main/java/com/jaino/common/extensions/ContextExt.kt b/android/core/common/src/main/java/com/jaino/common/extensions/ContextExt.kt
new file mode 100644
index 00000000..daf01264
--- /dev/null
+++ b/android/core/common/src/main/java/com/jaino/common/extensions/ContextExt.kt
@@ -0,0 +1,8 @@
+package com.jaino.common.extensions
+
+import android.content.Context
+import android.widget.Toast
+
+fun Context.showToast(message: String){
+ Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
+}
\ No newline at end of file
diff --git a/android/core/common/src/main/java/com/jaino/common/extensions/LongToDateExt.kt b/android/core/common/src/main/java/com/jaino/common/extensions/LongToDateExt.kt
new file mode 100644
index 00000000..1e47198c
--- /dev/null
+++ b/android/core/common/src/main/java/com/jaino/common/extensions/LongToDateExt.kt
@@ -0,0 +1,6 @@
+package com.jaino.common.extensions
+
+import java.text.SimpleDateFormat
+import java.util.*
+
+fun Long.toDateTime() : String = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.KOREAN).format(this)
\ No newline at end of file
diff --git a/android/core/common/src/main/java/com/jaino/common/extensions/SentimentExt.kt b/android/core/common/src/main/java/com/jaino/common/extensions/SentimentExt.kt
new file mode 100644
index 00000000..4e5a0c75
--- /dev/null
+++ b/android/core/common/src/main/java/com/jaino/common/extensions/SentimentExt.kt
@@ -0,0 +1,42 @@
+package com.jaino.common.extensions
+
+fun String.toTypedEng(): String{
+ return when(this){
+ "๋งฅ์ฃผ" -> "BEER"
+ "์์ฃผ" -> "SOJU"
+ "๋ง๊ฑธ๋ฆฌ" -> "MAKGEOLLI"
+ "์์ธ" -> "WINE"
+ "๋ฆฌํ๋ฅด" -> "LIQUEUR"
+ "์์คํค" -> "WHISKEY"
+ "์ฌ์ผ" -> "RICE_WINE"
+ "๊ผฌ๋" -> "BRANDY"
+ "๊ณผ์ค์ฃผ" -> "FRUIT"
+ "์ฝ์ฃผ" -> "YAKJU"
+ else -> ""
+ }
+}
+
+fun String.toTypedKor(): String{
+ return when(this){
+ "BEER" -> "๋งฅ์ฃผ"
+ "SOJU" -> "์์ฃผ"
+ "MAKGEOLLI" -> "๋ง๊ฑธ๋ฆฌ"
+ "WINE" -> "์์ธ"
+ "LIQUEUR" -> "๋ฆฌํ๋ฅด"
+ "WHISKEY" -> "์์คํค"
+ "RICE_WINE" -> "์ฌ์ผ"
+ "BRANDY" -> "๊ผฌ๋"
+ "FRUIT" -> "๊ณผ์ค์ฃผ"
+ "YAKJU" -> "์ฝ์ฃผ"
+ else -> ""
+ }
+}
+
+fun String.toSentimentKor(): String{
+ return when(this){
+ "SAD" -> "์ฌํ"
+ "HAPPY" -> "๊ธฐ์จ"
+ "MEDIAN" -> "ํ์จ"
+ else -> ""
+ }
+}
\ No newline at end of file
diff --git a/android/core/common/src/main/java/com/jaino/common/flow/EventFlow.kt b/android/core/common/src/main/java/com/jaino/common/flow/EventFlow.kt
new file mode 100644
index 00000000..104e9839
--- /dev/null
+++ b/android/core/common/src/main/java/com/jaino/common/flow/EventFlow.kt
@@ -0,0 +1,51 @@
+package com.jaino.common.flow
+
+import kotlinx.coroutines.InternalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.FlowCollector
+import kotlinx.coroutines.flow.MutableSharedFlow
+import java.util.concurrent.atomic.AtomicBoolean
+
+interface EventFlow : Flow {
+
+ companion object {
+ const val DEFAULT_REPLAY: Int = 3
+ }
+}
+
+interface MutableEventFlow : EventFlow, FlowCollector
+
+@Suppress("FunctionName")
+fun MutableEventFlow(
+ replay: Int = EventFlow.DEFAULT_REPLAY
+): MutableEventFlow = EventFlowImpl(replay)
+
+fun MutableEventFlow.asEventFlow(): EventFlow = ReadOnlyEventFlow(this)
+
+private class ReadOnlyEventFlow(flow: EventFlow) : EventFlow by flow
+
+private class EventFlowImpl(
+ replay: Int
+) : MutableEventFlow {
+
+ private val flow: MutableSharedFlow> = MutableSharedFlow(replay = replay)
+
+ @InternalCoroutinesApi
+ override suspend fun collect(collector: FlowCollector) = flow
+ .collect { slot ->
+ if (!slot.markConsumed()) {
+ collector.emit(slot.value)
+ }
+ }
+
+ override suspend fun emit(value: T) {
+ flow.emit(EventFlowSlot(value))
+ }
+}
+
+private class EventFlowSlot(val value: T) {
+
+ private val consumed: AtomicBoolean = AtomicBoolean(false)
+
+ fun markConsumed(): Boolean = consumed.getAndSet(true)
+}
\ No newline at end of file
diff --git a/android/core/common/src/main/java/com/jaino/common/model/UiModel.kt b/android/core/common/src/main/java/com/jaino/common/model/UiModel.kt
new file mode 100644
index 00000000..a3f32d3a
--- /dev/null
+++ b/android/core/common/src/main/java/com/jaino/common/model/UiModel.kt
@@ -0,0 +1,12 @@
+package com.jaino.common.model
+
+sealed class UiState {
+ object Init : UiState()
+ data class Success(val data: T) : UiState()
+ data class Failure(val message: String?) : UiState()
+}
+
+sealed class UiEvent {
+ data class Success(val data: T) : UiEvent()
+ data class Failure(val error: Throwable) : UiEvent()
+}
\ No newline at end of file
diff --git a/android/core/common/src/main/java/com/jaino/common/navigation/AppNavigator.kt b/android/core/common/src/main/java/com/jaino/common/navigation/AppNavigator.kt
new file mode 100644
index 00000000..2f351ced
--- /dev/null
+++ b/android/core/common/src/main/java/com/jaino/common/navigation/AppNavigator.kt
@@ -0,0 +1,11 @@
+package com.jaino.common.navigation
+
+import android.content.Intent
+
+interface AppNavigator {
+ fun navigateToAuth(): Intent
+
+ fun navigateToAccount(): Intent
+
+ fun navigateToHome(destination: String = ""): Intent
+}
\ No newline at end of file
diff --git a/android/core/common/src/main/java/com/jaino/common/utils/BindingAdapter.kt b/android/core/common/src/main/java/com/jaino/common/utils/BindingAdapter.kt
new file mode 100644
index 00000000..1a48f613
--- /dev/null
+++ b/android/core/common/src/main/java/com/jaino/common/utils/BindingAdapter.kt
@@ -0,0 +1,62 @@
+package com.jaino.common.utils
+
+import android.util.Base64
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.databinding.BindingAdapter
+import com.bumptech.glide.Glide
+import com.bumptech.glide.load.engine.DiskCacheStrategy
+import com.bumptech.glide.request.RequestOptions
+import com.jaino.common.extensions.toTypedKor
+
+object BindingAdapter {
+
+ @JvmStatic
+ @BindingAdapter("app:imageUrl")
+ fun loadImage(view: ImageView, src: String?) {
+ if (src != null) {
+ Glide.with(view.context)
+ .load(src)
+ .placeholder(com.jaino.designsystem.R.drawable.img)
+ .diskCacheStrategy(DiskCacheStrategy.NONE)
+ .apply(RequestOptions().fitCenter())
+ .error(com.jaino.designsystem.R.drawable.img)
+ .into(view)
+ }
+ }
+
+ @JvmStatic
+ @BindingAdapter("app:drawableId")
+ fun loadDrawable(view: ImageView, drawableId: Int) {
+ if (drawableId != 0) {
+ Glide.with(view.context)
+ .load(drawableId)
+ .placeholder(com.jaino.designsystem.R.drawable.img)
+ .diskCacheStrategy(DiskCacheStrategy.NONE)
+ .apply(RequestOptions().fitCenter())
+ .error(com.jaino.designsystem.R.drawable.img)
+ .into(view)
+ }
+ }
+
+ @JvmStatic
+ @BindingAdapter("app:encodedImage")
+ fun loadEncodedImage(view: ImageView, encodedImage: String) {
+ if (encodedImage.isNotEmpty()) {
+ val image: ByteArray = Base64.decode(encodedImage, Base64.DEFAULT)
+ Glide.with(view.context)
+ .load(image)
+ .placeholder(com.jaino.designsystem.R.drawable.img)
+ .diskCacheStrategy(DiskCacheStrategy.NONE)
+ .apply(RequestOptions().fitCenter())
+ .error(com.jaino.designsystem.R.drawable.img)
+ .into(view)
+ }
+ }
+
+ @JvmStatic
+ @BindingAdapter("app:typeName")
+ fun setKorTypeName(view: TextView, text: String) {
+ view.text = text.toTypedKor()
+ }
+}
\ No newline at end of file
diff --git a/android/core/common/src/main/java/com/jaino/common/utils/PhotoPickerChecker.kt b/android/core/common/src/main/java/com/jaino/common/utils/PhotoPickerChecker.kt
new file mode 100644
index 00000000..96c56cae
--- /dev/null
+++ b/android/core/common/src/main/java/com/jaino/common/utils/PhotoPickerChecker.kt
@@ -0,0 +1,20 @@
+package com.jaino.common.utils
+
+import android.os.Build
+import android.os.ext.SdkExtensions
+
+object PhotoPickerChecker {
+
+ private const val ANDROID_R_REQUIRED_EXTENSION_VERSION = 2
+
+ fun isPhotoPickerAvailable(): Boolean {
+ return when {
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> true
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
+ SdkExtensions.getExtensionVersion(Build.VERSION_CODES.R) >=
+ ANDROID_R_REQUIRED_EXTENSION_VERSION
+ }
+ else -> false
+ }
+ }
+}
diff --git a/android/core/common/src/main/java/com/jaino/common/utils/PickPhotoContract.kt b/android/core/common/src/main/java/com/jaino/common/utils/PickPhotoContract.kt
new file mode 100644
index 00000000..0b8ad551
--- /dev/null
+++ b/android/core/common/src/main/java/com/jaino/common/utils/PickPhotoContract.kt
@@ -0,0 +1,29 @@
+package com.jaino.common.utils
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.provider.MediaStore
+import androidx.activity.result.contract.ActivityResultContract
+
+class PickPhotoContract : ActivityResultContract() {
+
+ companion object{
+ const val MIME_TYPE_IMAGE = "image/*"
+ }
+
+ override fun createIntent(context: Context, input: Void?): Intent {
+ return Intent(if(PhotoPickerChecker.isPhotoPickerAvailable()){
+ Intent(MediaStore.ACTION_PICK_IMAGES)
+ }else{
+ Intent(Intent.ACTION_OPEN_DOCUMENT)
+ }).apply {
+ type = MIME_TYPE_IMAGE
+ }
+ }
+
+ override fun parseResult(resultCode: Int, intent: Intent?): Uri? {
+ return intent.takeIf { resultCode == Activity.RESULT_OK }?.data
+ }
+}
\ No newline at end of file
diff --git a/android/core/common/src/main/java/com/jaino/common/widget/ConfirmDialog.kt b/android/core/common/src/main/java/com/jaino/common/widget/ConfirmDialog.kt
new file mode 100644
index 00000000..69ad1284
--- /dev/null
+++ b/android/core/common/src/main/java/com/jaino/common/widget/ConfirmDialog.kt
@@ -0,0 +1,43 @@
+package com.jaino.common.widget
+
+import android.app.Dialog
+import android.content.Context
+import android.os.Bundle
+import android.view.WindowManager
+import com.jaino.common.databinding.DialogConfirmBinding
+
+class ConfirmDialog (
+ context : Context,
+ private val title : String,
+ private val onDoneButtonClick: () -> Unit
+): Dialog(context){
+
+ private val binding by lazy { DialogConfirmBinding.inflate(layoutInflater) }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ this.setContentView(binding.root)
+ initViews()
+ resizeDialog()
+ }
+
+ private fun initViews(){
+ binding.dialogConfirmTextView.text = title
+
+ binding.dialogConfirmPositiveButton.setOnClickListener{
+ onDoneButtonClick()
+ dismiss()
+ }
+
+ binding.dialogConfirmNegativeButton.setOnClickListener{
+ dismiss()
+ }
+ }
+
+ private fun resizeDialog(){
+ window?.setLayout(
+ WindowManager.LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.WRAP_CONTENT
+ )
+ }
+}
\ No newline at end of file
diff --git a/android/core/common/src/main/java/com/jaino/common/widget/ErrorDialog.kt b/android/core/common/src/main/java/com/jaino/common/widget/ErrorDialog.kt
new file mode 100644
index 00000000..3bf840a4
--- /dev/null
+++ b/android/core/common/src/main/java/com/jaino/common/widget/ErrorDialog.kt
@@ -0,0 +1,47 @@
+package com.jaino.common.widget
+
+import android.app.Dialog
+import android.content.Context
+import android.os.Bundle
+import android.view.View
+import com.jaino.common.databinding.DialogErrorBinding
+import java.net.ConnectException
+import java.net.HttpRetryException
+import java.net.SocketTimeoutException
+
+class ErrorDialog(
+ context: Context,
+ private val error: Throwable,
+ private val onRetryButtonClick : () -> Unit,
+) : Dialog(context){
+
+ private val binding by lazy{ DialogErrorBinding.inflate(layoutInflater) }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(binding.root)
+ setCanceledOnTouchOutside(false)
+ handleError()
+ }
+
+ private fun handleError(){
+ when (error) {
+ is HttpRetryException -> initViews("์ ์ํ ๋ค์ ์๋ํด์ฃผ์ธ์.", true)
+ is SocketTimeoutException, is ConnectException -> initViews(
+ "์ธํฐ๋ท ์ฐ๊ฒฐ์ ํ์ธํด์ฃผ์ธ์.", true
+ )
+ else -> initViews("ํ๋ฉด์ ๋ถ๋ฌ์ค๋๋ฐ ์คํจํ์์ต๋๋ค.", false)
+ }
+ }
+
+ private fun initViews(title: String, retryFlag: Boolean){
+ binding.dialogErrorText.text = title
+ if(!retryFlag){
+ binding.retryButton.visibility = View.GONE
+ }
+ binding.retryButton.setOnClickListener{
+ onRetryButtonClick()
+ dismiss()
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/core/common/src/main/res/layout/dialog_confirm.xml b/android/core/common/src/main/res/layout/dialog_confirm.xml
new file mode 100644
index 00000000..6324ecb1
--- /dev/null
+++ b/android/core/common/src/main/res/layout/dialog_confirm.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/core/common/src/main/res/layout/dialog_error.xml b/android/core/common/src/main/res/layout/dialog_error.xml
new file mode 100644
index 00000000..f8c4d458
--- /dev/null
+++ b/android/core/common/src/main/res/layout/dialog_error.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/core/common/src/test/java/com/jaino/common/ExampleUnitTest.kt b/android/core/common/src/test/java/com/jaino/common/ExampleUnitTest.kt
new file mode 100644
index 00000000..d74d4e2c
--- /dev/null
+++ b/android/core/common/src/test/java/com/jaino/common/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.jaino.common
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * 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)
+ }
+}
\ No newline at end of file
diff --git a/android/core/data/.gitignore b/android/core/data/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/android/core/data/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/android/core/data/build.gradle.kts b/android/core/data/build.gradle.kts
new file mode 100644
index 00000000..a6c0f491
--- /dev/null
+++ b/android/core/data/build.gradle.kts
@@ -0,0 +1,25 @@
+plugins {
+ id("com.jaino.feature")
+ id("com.jaino.serialization")
+ id("com.jaino.hilt")
+}
+
+android {
+ defaultConfig {
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+ namespace = "com.jaino.data"
+}
+
+dependencies {
+ implementation(project(":core:network"))
+ implementation(project(":core:datastore"))
+ implementation(project(":core:model"))
+ implementation(libs.bundles.kotlin)
+ implementation(libs.kakao.login)
+ implementation(libs.timber)
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.androidx.test.junit)
+ androidTestImplementation(libs.androidx.test.espresso)
+}
diff --git a/android/core/data/consumer-rules.pro b/android/core/data/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/android/core/data/proguard-rules.pro b/android/core/data/proguard-rules.pro
new file mode 100644
index 00000000..ff59496d
--- /dev/null
+++ b/android/core/data/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.kts.
+#
+# 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/android/core/data/src/androidTest/java/com/jaino/data/ExampleInstrumentedTest.kt b/android/core/data/src/androidTest/java/com/jaino/data/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..6344684d
--- /dev/null
+++ b/android/core/data/src/androidTest/java/com/jaino/data/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.jaino.data
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.jaino.data.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/android/core/data/src/main/AndroidManifest.xml b/android/core/data/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a5918e68
--- /dev/null
+++ b/android/core/data/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/android/core/data/src/main/java/com/jaino/data/di/RepositoryModule.kt b/android/core/data/src/main/java/com/jaino/data/di/RepositoryModule.kt
new file mode 100644
index 00000000..99098f92
--- /dev/null
+++ b/android/core/data/src/main/java/com/jaino/data/di/RepositoryModule.kt
@@ -0,0 +1,77 @@
+package com.jaino.data.di
+
+import com.jaino.data.repository.analysis.AnalysisRepository
+import com.jaino.data.repository.auth.AuthRepository
+import com.jaino.data.repository.auth.AuthRepositoryImpl
+import com.jaino.data.repository.dictionary.DrinksRepository
+import com.jaino.data.repository.dictionary.DrinksRepositoryImpl
+import com.jaino.data.repository.review.ReviewRepository
+import com.jaino.data.repository.review.ReviewRepositoryImpl
+import com.jaino.data.repository.user.LocalUserRepository
+import com.jaino.data.repository.user.LocalUserRepositoryImpl
+import com.jaino.data.repository.analysis.AnalysisRepositoryImpl
+import com.jaino.data.repository.rank.RankRepository
+import com.jaino.data.repository.rank.RankRepositoryImpl
+import com.jaino.data.repository.setting.ProfileRepository
+import com.jaino.data.repository.setting.ProfileRepositoryImpl
+import com.jaino.datastore.BeJuRyuDatastore
+import com.jaino.network.datasource.analysis.AnalysisDataSource
+import com.jaino.network.datasource.auth.AuthDataSource
+import com.jaino.network.datasource.dictionary.DrinkDataSource
+import com.jaino.network.datasource.rank.RankDataSource
+import com.jaino.network.datasource.review.ReviewDataSource
+import com.jaino.network.datasource.setting.ProfileDataSource
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+object RepositoryModule {
+
+ @Singleton
+ @Provides
+ fun provideSocialAuthRepository(
+ dataSource: AuthDataSource,
+ dataStore : BeJuRyuDatastore
+ ) : AuthRepository = AuthRepositoryImpl(dataSource, dataStore)
+
+ @Singleton
+ @Provides
+ fun provideLocalUserRepository(
+ dataStore: BeJuRyuDatastore
+ ): LocalUserRepository = LocalUserRepositoryImpl(dataStore)
+
+ @Singleton
+ @Provides
+ fun provideAnalysisRepository(
+ source : AnalysisDataSource
+ ): AnalysisRepository = AnalysisRepositoryImpl(source)
+
+
+ @Singleton
+ @Provides
+ fun provideDrinkRepository(
+ drinkDataSource : DrinkDataSource,
+ ): DrinksRepository = DrinksRepositoryImpl(drinkDataSource)
+
+ @Singleton
+ @Provides
+ fun provideReviewRepository(
+ reviewDataSource: ReviewDataSource,
+ ): ReviewRepository = ReviewRepositoryImpl(reviewDataSource)
+
+ @Singleton
+ @Provides
+ fun provideRankRepository(
+ RankDataSource: RankDataSource,
+ ): RankRepository = RankRepositoryImpl(RankDataSource)
+
+ @Singleton
+ @Provides
+ fun provideProfileRepository(
+ profileDataSource: ProfileDataSource,
+ ): ProfileRepository = ProfileRepositoryImpl(profileDataSource)
+}
diff --git a/android/core/data/src/main/java/com/jaino/data/di/SocialRepositoryModule.kt b/android/core/data/src/main/java/com/jaino/data/di/SocialRepositoryModule.kt
new file mode 100644
index 00000000..4cac9ecb
--- /dev/null
+++ b/android/core/data/src/main/java/com/jaino/data/di/SocialRepositoryModule.kt
@@ -0,0 +1,20 @@
+package com.jaino.data.di
+
+import com.jaino.data.repository.auth.SocialAuthRepository
+import com.jaino.data.repository.auth.SocialAuthRepositoryImpl
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.components.ActivityComponent
+
+@Module
+@InstallIn(ActivityComponent::class)
+object SocialRepositoryModule {
+
+ // activity Scope not use singleton
+ @Provides
+ fun provideSocialAuthRepository(
+ repositoryImpl : SocialAuthRepositoryImpl
+ ) : SocialAuthRepository = repositoryImpl
+
+}
diff --git a/android/core/data/src/main/java/com/jaino/data/model/auth/KakaoToken.kt b/android/core/data/src/main/java/com/jaino/data/model/auth/KakaoToken.kt
new file mode 100644
index 00000000..25933e34
--- /dev/null
+++ b/android/core/data/src/main/java/com/jaino/data/model/auth/KakaoToken.kt
@@ -0,0 +1,3 @@
+package com.jaino.data.model.auth
+
+data class KakaoToken (val token : String)
diff --git a/android/core/data/src/main/java/com/jaino/data/model/review/ReviewRequest.kt b/android/core/data/src/main/java/com/jaino/data/model/review/ReviewRequest.kt
new file mode 100644
index 00000000..f928001c
--- /dev/null
+++ b/android/core/data/src/main/java/com/jaino/data/model/review/ReviewRequest.kt
@@ -0,0 +1,8 @@
+package com.jaino.data.model.review
+
+data class ReviewRequest(
+ val userId : Long,
+ val comment : String,
+ val score : Int,
+ val date : String
+)
\ No newline at end of file
diff --git a/android/core/data/src/main/java/com/jaino/data/repository/analysis/AnalysisRepository.kt b/android/core/data/src/main/java/com/jaino/data/repository/analysis/AnalysisRepository.kt
new file mode 100644
index 00000000..df1357a5
--- /dev/null
+++ b/android/core/data/src/main/java/com/jaino/data/repository/analysis/AnalysisRepository.kt
@@ -0,0 +1,15 @@
+package com.jaino.data.repository.analysis
+
+import com.jaino.model.analysis.AnalysisId
+import com.jaino.model.analysis.SentimentAnalysis
+import com.jaino.model.analysis.AnalysisHistory
+
+interface AnalysisRepository {
+
+ suspend fun postAnalysisSource(
+ date: String, textExpression: String, facialExpression: String
+ ): Result
+ suspend fun getAnalysisList() : Result>
+
+ suspend fun getSentimentAnalysis(analysisId: Long) : Result
+}
\ No newline at end of file
diff --git a/android/core/data/src/main/java/com/jaino/data/repository/analysis/AnalysisRepositoryImpl.kt b/android/core/data/src/main/java/com/jaino/data/repository/analysis/AnalysisRepositoryImpl.kt
new file mode 100644
index 00000000..ad87216b
--- /dev/null
+++ b/android/core/data/src/main/java/com/jaino/data/repository/analysis/AnalysisRepositoryImpl.kt
@@ -0,0 +1,32 @@
+package com.jaino.data.repository.analysis
+
+import com.jaino.model.analysis.AnalysisId
+import com.jaino.model.analysis.SentimentAnalysis
+import com.jaino.model.analysis.AnalysisHistory
+import com.jaino.network.datasource.analysis.AnalysisDataSource
+import com.jaino.network.model.request.analysis.AnalysisSourceRequest
+import javax.inject.Inject
+
+class AnalysisRepositoryImpl @Inject constructor(
+ private val source: AnalysisDataSource,
+) : AnalysisRepository {
+
+ override suspend fun postAnalysisSource(
+ date: String,
+ textExpression: String,
+ facialExpression: String
+ ): Result =
+ source.postAnalysisSource(
+ AnalysisSourceRequest(date, textExpression, facialExpression)
+ ).mapCatching {
+ it.toAnalysisId()
+ }
+
+ override suspend fun getAnalysisList(): Result> =
+ source.getAnalysisList().mapCatching { list ->
+ list.map{ it.toAnalyzeHistory() }
+ }
+
+ override suspend fun getSentimentAnalysis(analysisId: Long): Result =
+ source.getSentimentAnalysis(analysisId).mapCatching { it.toSentimentAnalysis() }
+}
\ No newline at end of file
diff --git a/android/core/data/src/main/java/com/jaino/data/repository/auth/AuthRepository.kt b/android/core/data/src/main/java/com/jaino/data/repository/auth/AuthRepository.kt
new file mode 100644
index 00000000..bdae3f82
--- /dev/null
+++ b/android/core/data/src/main/java/com/jaino/data/repository/auth/AuthRepository.kt
@@ -0,0 +1,14 @@
+package com.jaino.data.repository.auth
+
+interface AuthRepository {
+ suspend fun signInService(token: String) : Result
+
+ fun setAccessToken(token: String)
+
+ fun setRefreshToken(token: String)
+
+ fun setNickName(nickName: String)
+
+ fun setUserId(userId: Long)
+
+}
\ No newline at end of file
diff --git a/android/core/data/src/main/java/com/jaino/data/repository/auth/AuthRepositoryImpl.kt b/android/core/data/src/main/java/com/jaino/data/repository/auth/AuthRepositoryImpl.kt
new file mode 100644
index 00000000..6b8753ea
--- /dev/null
+++ b/android/core/data/src/main/java/com/jaino/data/repository/auth/AuthRepositoryImpl.kt
@@ -0,0 +1,41 @@
+package com.jaino.data.repository.auth
+
+import com.jaino.datastore.BeJuRyuDatastore
+import com.jaino.network.datasource.auth.AuthDataSource
+import javax.inject.Inject
+
+class AuthRepositoryImpl @Inject constructor(
+ private val source: AuthDataSource,
+ private val datastore: BeJuRyuDatastore
+) : AuthRepository {
+ override suspend fun signInService(token: String) : Result {
+ runCatching { source.signIn(token).toSignIn() }
+ .onSuccess {
+ setAccessToken(it.accessToken)
+ setRefreshToken(it.refreshToken)
+ setUserId(it.userId)
+ setNickName(it.nickName)
+ return Result.success(Unit)
+ }
+ .onFailure{
+ return Result.failure(it)
+ }
+ return Result.failure(Throwable("AuthRepositoryImpl UnKnown error"))
+ }
+
+ override fun setAccessToken(token: String) {
+ datastore.accessToken = token
+ }
+
+ override fun setRefreshToken(token: String) {
+ datastore.refreshToken = token
+ }
+
+ override fun setUserId(userId: Long) {
+ datastore.userId = userId
+ }
+
+ override fun setNickName(nickName: String) {
+ datastore.nickName = nickName
+ }
+}
\ No newline at end of file
diff --git a/android/core/data/src/main/java/com/jaino/data/repository/auth/SocialAuthRepository.kt b/android/core/data/src/main/java/com/jaino/data/repository/auth/SocialAuthRepository.kt
new file mode 100644
index 00000000..479de99c
--- /dev/null
+++ b/android/core/data/src/main/java/com/jaino/data/repository/auth/SocialAuthRepository.kt
@@ -0,0 +1,16 @@
+package com.jaino.data.repository.auth
+
+import com.jaino.data.model.auth.KakaoToken
+
+interface SocialAuthRepository {
+
+ val isKakaoTalkLoginAvailable: Boolean
+
+ suspend fun signInByKakaoTalk(): Result
+
+ suspend fun signInByKakaoAccount(): Result
+
+ fun signOut()
+
+ fun unlink()
+}
diff --git a/android/core/data/src/main/java/com/jaino/data/repository/auth/SocialAuthRepositoryImpl.kt b/android/core/data/src/main/java/com/jaino/data/repository/auth/SocialAuthRepositoryImpl.kt
new file mode 100644
index 00000000..122f662d
--- /dev/null
+++ b/android/core/data/src/main/java/com/jaino/data/repository/auth/SocialAuthRepositoryImpl.kt
@@ -0,0 +1,62 @@
+package com.jaino.data.repository.auth
+
+import android.content.Context
+import com.jaino.data.model.auth.KakaoToken
+import com.kakao.sdk.user.UserApiClient
+import dagger.hilt.android.qualifiers.ActivityContext
+import kotlinx.coroutines.suspendCancellableCoroutine
+import timber.log.Timber
+import javax.inject.Inject
+import kotlin.coroutines.resume
+import kotlin.coroutines.resumeWithException
+
+class SocialAuthRepositoryImpl @Inject constructor(
+ private val kakaoClient: UserApiClient,
+ @ActivityContext private val context: Context
+) : SocialAuthRepository {
+
+ override val isKakaoTalkLoginAvailable: Boolean
+ get() = kakaoClient.isKakaoTalkLoginAvailable(context)
+
+ override suspend fun signInByKakaoTalk(): Result =
+ suspendCancellableCoroutine {
+ kakaoClient.loginWithKakaoTalk(context){ token, error ->
+ if (error != null) {
+ it.resume(Result.failure(error))
+ return@loginWithKakaoTalk
+ }
+ if (token != null) {
+ it.resume(Result.success(KakaoToken(token.accessToken)))
+ return@loginWithKakaoTalk
+ }
+ it.resumeWithException(Throwable("์ ์ ์๋ ์ค๋ฅ๊ฐ ๋ฐ์ํ์์ต๋๋ค."))
+ }
+ }
+
+ override suspend fun signInByKakaoAccount(): Result =
+ suspendCancellableCoroutine {
+ kakaoClient.loginWithKakaoAccount(context) { token, error ->
+ if (error != null) {
+ it.resume(Result.failure(error))
+ return@loginWithKakaoAccount
+ }
+ if (token != null) {
+ it.resume(Result.success(KakaoToken(token.accessToken)))
+ return@loginWithKakaoAccount
+ }
+ it.resumeWithException(Throwable("์ ์ ์๋ ์ค๋ฅ๊ฐ ๋ฐ์ํ์์ต๋๋ค."))
+ }
+ }
+
+ override fun signOut() {
+ kakaoClient.logout{
+ Timber.e(it)
+ }
+ }
+
+ override fun unlink() {
+ kakaoClient.unlink {
+ Timber.e(it)
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/core/data/src/main/java/com/jaino/data/repository/dictionary/DrinksRepository.kt b/android/core/data/src/main/java/com/jaino/data/repository/dictionary/DrinksRepository.kt
new file mode 100644
index 00000000..4c918e29
--- /dev/null
+++ b/android/core/data/src/main/java/com/jaino/data/repository/dictionary/DrinksRepository.kt
@@ -0,0 +1,13 @@
+package com.jaino.data.repository.dictionary
+
+import com.jaino.model.dictionary.Drink
+
+interface DrinksRepository {
+ suspend fun getDrinkList(): Result>
+
+ suspend fun getDrinkListByType(type: String): Result>
+
+ suspend fun getDrinkListByName(name: String): Result>
+
+ suspend fun getDrinkDataById(id: Long): Result
+}
\ No newline at end of file
diff --git a/android/core/data/src/main/java/com/jaino/data/repository/dictionary/DrinksRepositoryImpl.kt b/android/core/data/src/main/java/com/jaino/data/repository/dictionary/DrinksRepositoryImpl.kt
new file mode 100644
index 00000000..47dbf13a
--- /dev/null
+++ b/android/core/data/src/main/java/com/jaino/data/repository/dictionary/DrinksRepositoryImpl.kt
@@ -0,0 +1,33 @@
+package com.jaino.data.repository.dictionary
+
+import com.jaino.model.dictionary.Drink
+import com.jaino.network.datasource.dictionary.DrinkDataSource
+import javax.inject.Inject
+
+class DrinksRepositoryImpl @Inject constructor(
+ private val drinkDataSource: DrinkDataSource,
+) : DrinksRepository{
+ override suspend fun getDrinkList(): Result> {
+ return drinkDataSource.getDrinkList().mapCatching{ list ->
+ list.map{ data -> data.toDrinkInfo()}
+ }
+ }
+
+ override suspend fun getDrinkListByType(type: String): Result> {
+ return drinkDataSource.getDrinkListByType(type).mapCatching { list ->
+ list.drinks.map { data -> data.toDrinkInfo() }
+ }
+ }
+
+ override suspend fun getDrinkListByName(name: String): Result> {
+ return drinkDataSource.getDrinkByName(name).mapCatching { list ->
+ list.drinks.map { data -> data.toDrinkInfo() }
+ }
+ }
+
+ override suspend fun getDrinkDataById(id: Long): Result {
+ return drinkDataSource.getDrinkById(id).mapCatching { data ->
+ data.toDrinkInfo()
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/core/data/src/main/java/com/jaino/data/repository/rank/RankRepository.kt b/android/core/data/src/main/java/com/jaino/data/repository/rank/RankRepository.kt
new file mode 100644
index 00000000..18561d13
--- /dev/null
+++ b/android/core/data/src/main/java/com/jaino/data/repository/rank/RankRepository.kt
@@ -0,0 +1,9 @@
+package com.jaino.data.repository.rank
+
+import com.jaino.model.rank.Rank
+
+interface RankRepository{
+ suspend fun getHighestRatedList(): Result>
+
+ suspend fun getMostReviewedList(): Result>
+}
\ No newline at end of file
diff --git a/android/core/data/src/main/java/com/jaino/data/repository/rank/RankRepositoryImpl.kt b/android/core/data/src/main/java/com/jaino/data/repository/rank/RankRepositoryImpl.kt
new file mode 100644
index 00000000..76151ff6
--- /dev/null
+++ b/android/core/data/src/main/java/com/jaino/data/repository/rank/RankRepositoryImpl.kt
@@ -0,0 +1,21 @@
+package com.jaino.data.repository.rank
+
+import com.jaino.model.rank.Rank
+import com.jaino.network.datasource.rank.RankDataSource
+import javax.inject.Inject
+
+class RankRepositoryImpl @Inject constructor(
+ private val dataSource: RankDataSource
+): RankRepository{
+ override suspend fun getHighestRatedList(): Result> {
+ return dataSource.getHighestRatedList().mapCatching { list ->
+ list.ranking.map{ it.toRank() }
+ }
+ }
+
+ override suspend fun getMostReviewedList(): Result> {
+ return dataSource.getMostReviewedList().mapCatching { list ->
+ list.ranking.map { it.toRank() }
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/core/data/src/main/java/com/jaino/data/repository/review/ReviewRepository.kt b/android/core/data/src/main/java/com/jaino/data/repository/review/ReviewRepository.kt
new file mode 100644
index 00000000..a07e3bee
--- /dev/null
+++ b/android/core/data/src/main/java/com/jaino/data/repository/review/ReviewRepository.kt
@@ -0,0 +1,10 @@
+package com.jaino.data.repository.review
+
+import com.jaino.data.model.review.ReviewRequest
+import com.jaino.model.review.Review
+
+interface ReviewRepository {
+ suspend fun getReviewList(drinkId : Long): Result>
+
+ suspend fun postReview(drinkId: Long, review : ReviewRequest) : Result
+}
\ No newline at end of file
diff --git a/android/core/data/src/main/java/com/jaino/data/repository/review/ReviewRepositoryImpl.kt b/android/core/data/src/main/java/com/jaino/data/repository/review/ReviewRepositoryImpl.kt
new file mode 100644
index 00000000..4f0e558e
--- /dev/null
+++ b/android/core/data/src/main/java/com/jaino/data/repository/review/ReviewRepositoryImpl.kt
@@ -0,0 +1,24 @@
+package com.jaino.data.repository.review
+
+import com.jaino.data.model.review.ReviewRequest
+import com.jaino.model.review.Review
+import com.jaino.network.datasource.review.ReviewDataSource
+import com.jaino.network.model.request.review.WriteDrinkReviewRequest
+import javax.inject.Inject
+
+class ReviewRepositoryImpl @Inject constructor(
+ private val reviewDataSource: ReviewDataSource
+) : ReviewRepository{
+ override suspend fun getReviewList(drinkId : Long): Result> {
+ return reviewDataSource.getReviewList(drinkId).mapCatching { it.map{ it.toDrinkReview() } }
+ }
+
+ override suspend fun postReview(drinkId: Long, review : ReviewRequest): Result {
+ return reviewDataSource.postReview(drinkId, WriteDrinkReviewRequest(
+ userId = review.userId,
+ comment = review.comment,
+ score = review.score,
+ date = review.date
+ ))
+ }
+}
\ No newline at end of file
diff --git a/android/core/data/src/main/java/com/jaino/data/repository/setting/ProfileRepository.kt b/android/core/data/src/main/java/com/jaino/data/repository/setting/ProfileRepository.kt
new file mode 100644
index 00000000..3705faa5
--- /dev/null
+++ b/android/core/data/src/main/java/com/jaino/data/repository/setting/ProfileRepository.kt
@@ -0,0 +1,9 @@
+package com.jaino.data.repository.setting
+
+import com.jaino.model.setting.Profile
+
+interface ProfileRepository {
+ suspend fun getProfile(): Result
+
+ suspend fun editNickname(userId: Long, nickname: String): Result
+}
\ No newline at end of file
diff --git a/android/core/data/src/main/java/com/jaino/data/repository/setting/ProfileRepositoryImpl.kt b/android/core/data/src/main/java/com/jaino/data/repository/setting/ProfileRepositoryImpl.kt
new file mode 100644
index 00000000..56ce5242
--- /dev/null
+++ b/android/core/data/src/main/java/com/jaino/data/repository/setting/ProfileRepositoryImpl.kt
@@ -0,0 +1,20 @@
+package com.jaino.data.repository.setting
+
+import com.jaino.model.setting.Profile
+import com.jaino.network.datasource.setting.ProfileDataSource
+import com.jaino.network.model.request.setting.ProfileRequest
+import javax.inject.Inject
+
+class ProfileRepositoryImpl @Inject constructor(
+ private val dataSource: ProfileDataSource
+): ProfileRepository{
+ override suspend fun getProfile(): Result {
+ return dataSource.getProfile().mapCatching { it.toProfile() }
+ }
+
+ override suspend fun editNickname(userId: Long, nickname: String): Result {
+ return dataSource.editNickname(ProfileRequest(userId, nickname)).mapCatching {
+ it.toProfile()
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/core/data/src/main/java/com/jaino/data/repository/user/LocalUserRepository.kt b/android/core/data/src/main/java/com/jaino/data/repository/user/LocalUserRepository.kt
new file mode 100644
index 00000000..0098dd29
--- /dev/null
+++ b/android/core/data/src/main/java/com/jaino/data/repository/user/LocalUserRepository.kt
@@ -0,0 +1,12 @@
+package com.jaino.data.repository.user
+
+interface LocalUserRepository {
+
+ suspend fun setNickname(nickname: String)
+
+ suspend fun getNickName() : String
+
+ suspend fun getUserId() : Long
+
+ fun clear()
+}
\ No newline at end of file
diff --git a/android/core/data/src/main/java/com/jaino/data/repository/user/LocalUserRepositoryImpl.kt b/android/core/data/src/main/java/com/jaino/data/repository/user/LocalUserRepositoryImpl.kt
new file mode 100644
index 00000000..98f890a2
--- /dev/null
+++ b/android/core/data/src/main/java/com/jaino/data/repository/user/LocalUserRepositoryImpl.kt
@@ -0,0 +1,19 @@
+package com.jaino.data.repository.user
+
+import com.jaino.datastore.BeJuRyuDatastore
+import javax.inject.Inject
+
+class LocalUserRepositoryImpl @Inject constructor(
+ private val dataStore : BeJuRyuDatastore
+) : LocalUserRepository{
+
+ override suspend fun setNickname(nickname: String){
+ dataStore.nickName = nickname
+ }
+
+ override suspend fun getNickName(): String = dataStore.nickName
+
+ override suspend fun getUserId(): Long = dataStore.userId
+
+ override fun clear() = dataStore.clear()
+}
\ No newline at end of file
diff --git a/android/core/data/src/test/java/com/jaino/data/ExampleUnitTest.kt b/android/core/data/src/test/java/com/jaino/data/ExampleUnitTest.kt
new file mode 100644
index 00000000..3cb928d6
--- /dev/null
+++ b/android/core/data/src/test/java/com/jaino/data/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.jaino.data
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * 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)
+ }
+}
\ No newline at end of file
diff --git a/android/core/datastore/.gitignore b/android/core/datastore/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/android/core/datastore/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/android/core/datastore/build.gradle.kts b/android/core/datastore/build.gradle.kts
new file mode 100644
index 00000000..15a8e72f
--- /dev/null
+++ b/android/core/datastore/build.gradle.kts
@@ -0,0 +1,18 @@
+plugins {
+ id("com.jaino.feature")
+ id("com.jaino.serialization")
+ id("com.jaino.hilt")
+}
+
+android {
+ namespace = "com.jaino.datastore"
+}
+
+dependencies {
+ implementation(project(":core:model"))
+ implementation(libs.bundles.kotlin)
+ implementation(libs.androidx.security)
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.androidx.test.junit)
+ androidTestImplementation(libs.androidx.test.espresso)
+}
\ No newline at end of file
diff --git a/android/core/datastore/consumer-rules.pro b/android/core/datastore/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/android/core/datastore/proguard-rules.pro b/android/core/datastore/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/android/core/datastore/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/android/core/datastore/src/androidTest/java/com/jaino/datastore/ExampleInstrumentedTest.kt b/android/core/datastore/src/androidTest/java/com/jaino/datastore/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..37526c6d
--- /dev/null
+++ b/android/core/datastore/src/androidTest/java/com/jaino/datastore/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.jaino.datastore
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.jaino.datastore.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/android/core/datastore/src/main/AndroidManifest.xml b/android/core/datastore/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a5918e68
--- /dev/null
+++ b/android/core/datastore/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/android/core/datastore/src/main/java/com/jaino/datastore/BeJuRyuDatastore.kt b/android/core/datastore/src/main/java/com/jaino/datastore/BeJuRyuDatastore.kt
new file mode 100644
index 00000000..973abbb0
--- /dev/null
+++ b/android/core/datastore/src/main/java/com/jaino/datastore/BeJuRyuDatastore.kt
@@ -0,0 +1,10 @@
+package com.jaino.datastore
+
+interface BeJuRyuDatastore {
+ var accessToken: String
+ var refreshToken: String
+ var nickName : String
+ var userId: Long
+
+ fun clear()
+}
diff --git a/android/core/datastore/src/main/java/com/jaino/datastore/BeJuRyuDatastoreImpl.kt b/android/core/datastore/src/main/java/com/jaino/datastore/BeJuRyuDatastoreImpl.kt
new file mode 100644
index 00000000..ceb93822
--- /dev/null
+++ b/android/core/datastore/src/main/java/com/jaino/datastore/BeJuRyuDatastoreImpl.kt
@@ -0,0 +1,46 @@
+package com.jaino.datastore
+
+import android.content.Context
+import androidx.core.content.edit
+import androidx.security.crypto.EncryptedSharedPreferences
+import androidx.security.crypto.MasterKeys
+import dagger.hilt.android.qualifiers.ApplicationContext
+import javax.inject.Inject
+
+const val FILE_NAME = "BEJURYU_DATA"
+
+class BeJuRyuDatastoreImpl @Inject constructor(
+ @ApplicationContext context: Context
+) : BeJuRyuDatastore{
+
+ private val storeDelegate by lazy {
+ if (BuildConfig.DEBUG) context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE)
+ else EncryptedSharedPreferences.create(
+ FILE_NAME,
+ MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
+ context,
+ EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
+ EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
+ )
+ }
+
+ override var accessToken: String
+ set(value) = storeDelegate.edit { putString("ACCESS_TOKEN", value) }
+ get() = storeDelegate.getString("ACCESS_TOKEN", "") ?: ""
+
+ override var refreshToken: String
+ set(value) = storeDelegate.edit { putString("REFRESH_TOKEN", value) }
+ get() = storeDelegate.getString("REFRESH_TOKEN", "") ?: ""
+
+ override var userId: Long
+ set(value) = storeDelegate.edit { putLong("USER_ID", value) }
+ get() = storeDelegate.getLong("USER_ID", -1)
+
+ override var nickName: String
+ set(value) = storeDelegate.edit{ putString("NICK_NAME", value) }
+ get() = storeDelegate.getString("NICK_NAME", "") ?: ""
+
+ override fun clear() {
+ storeDelegate.edit { clear() }
+ }
+}
diff --git a/android/core/datastore/src/main/java/com/jaino/datastore/di/DataStoreModule.kt b/android/core/datastore/src/main/java/com/jaino/datastore/di/DataStoreModule.kt
new file mode 100644
index 00000000..576c25c8
--- /dev/null
+++ b/android/core/datastore/src/main/java/com/jaino/datastore/di/DataStoreModule.kt
@@ -0,0 +1,20 @@
+package com.jaino.datastore.di
+
+import com.jaino.datastore.BeJuRyuDatastore
+import com.jaino.datastore.BeJuRyuDatastoreImpl
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+abstract class DataStoreModule {
+
+ @Binds
+ @Singleton
+ abstract fun provideBeJuRyuDataBase(
+ beJuRyuDatastore: BeJuRyuDatastoreImpl
+ ): BeJuRyuDatastore
+}
\ No newline at end of file
diff --git a/android/core/datastore/src/test/java/com/jaino/datastore/ExampleUnitTest.kt b/android/core/datastore/src/test/java/com/jaino/datastore/ExampleUnitTest.kt
new file mode 100644
index 00000000..6975056b
--- /dev/null
+++ b/android/core/datastore/src/test/java/com/jaino/datastore/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.jaino.datastore
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * 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)
+ }
+}
\ No newline at end of file
diff --git a/android/core/designsystem/.gitignore b/android/core/designsystem/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/android/core/designsystem/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/android/core/designsystem/build.gradle.kts b/android/core/designsystem/build.gradle.kts
new file mode 100644
index 00000000..1ecc1d22
--- /dev/null
+++ b/android/core/designsystem/build.gradle.kts
@@ -0,0 +1,12 @@
+plugins {
+ alias(libs.plugins.android.library)
+}
+
+android{
+ namespace = "com.jaino.designsystem"
+ compileSdk = 33
+}
+
+dependencies {
+ implementation(libs.material)
+}
\ No newline at end of file
diff --git a/android/core/designsystem/consumer-rules.pro b/android/core/designsystem/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/android/core/designsystem/src/androidTest/java/com/jaino/designsystem/ExampleInstrumentedTest.kt b/android/core/designsystem/src/androidTest/java/com/jaino/designsystem/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..7d87cb12
--- /dev/null
+++ b/android/core/designsystem/src/androidTest/java/com/jaino/designsystem/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.jaino.designsystem
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.jaino.designsystem.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/android/core/designsystem/src/main/AndroidManifest.xml b/android/core/designsystem/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a5918e68
--- /dev/null
+++ b/android/core/designsystem/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/android/core/designsystem/src/main/res/drawable/back_button.xml b/android/core/designsystem/src/main/res/drawable/back_button.xml
new file mode 100644
index 00000000..14180d92
--- /dev/null
+++ b/android/core/designsystem/src/main/res/drawable/back_button.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/android/core/designsystem/src/main/res/drawable/baseline_32.xml b/android/core/designsystem/src/main/res/drawable/baseline_32.xml
new file mode 100644
index 00000000..24815824
--- /dev/null
+++ b/android/core/designsystem/src/main/res/drawable/baseline_32.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/android/core/designsystem/src/main/res/drawable/baseline_back_32.xml b/android/core/designsystem/src/main/res/drawable/baseline_back_32.xml
new file mode 100644
index 00000000..24815824
--- /dev/null
+++ b/android/core/designsystem/src/main/res/drawable/baseline_back_32.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/android/core/designsystem/src/main/res/drawable/baseline_checklist_24.xml b/android/core/designsystem/src/main/res/drawable/baseline_checklist_24.xml
new file mode 100644
index 00000000..4888e908
--- /dev/null
+++ b/android/core/designsystem/src/main/res/drawable/baseline_checklist_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/android/core/designsystem/src/main/res/drawable/baseline_close_32.xml b/android/core/designsystem/src/main/res/drawable/baseline_close_32.xml
new file mode 100644
index 00000000..4b64e31d
--- /dev/null
+++ b/android/core/designsystem/src/main/res/drawable/baseline_close_32.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/android/core/designsystem/src/main/res/drawable/baseline_error_outline_24.xml b/android/core/designsystem/src/main/res/drawable/baseline_error_outline_24.xml
new file mode 100644
index 00000000..7816afd1
--- /dev/null
+++ b/android/core/designsystem/src/main/res/drawable/baseline_error_outline_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/android/core/designsystem/src/main/res/drawable/baseline_forward_24.xml b/android/core/designsystem/src/main/res/drawable/baseline_forward_24.xml
new file mode 100644
index 00000000..f988e7d8
--- /dev/null
+++ b/android/core/designsystem/src/main/res/drawable/baseline_forward_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/android/core/designsystem/src/main/res/drawable/baseline_ios_share_24.xml b/android/core/designsystem/src/main/res/drawable/baseline_ios_share_24.xml
new file mode 100644
index 00000000..d4265167
--- /dev/null
+++ b/android/core/designsystem/src/main/res/drawable/baseline_ios_share_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/android/core/designsystem/src/main/res/drawable/baseline_menu_book_24.xml b/android/core/designsystem/src/main/res/drawable/baseline_menu_book_24.xml
new file mode 100644
index 00000000..911877c4
--- /dev/null
+++ b/android/core/designsystem/src/main/res/drawable/baseline_menu_book_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/android/core/designsystem/src/main/res/drawable/baseline_person_24.xml b/android/core/designsystem/src/main/res/drawable/baseline_person_24.xml
new file mode 100644
index 00000000..98730cd9
--- /dev/null
+++ b/android/core/designsystem/src/main/res/drawable/baseline_person_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/android/core/designsystem/src/main/res/drawable/baseline_replay_24.xml b/android/core/designsystem/src/main/res/drawable/baseline_replay_24.xml
new file mode 100644
index 00000000..54bdae9f
--- /dev/null
+++ b/android/core/designsystem/src/main/res/drawable/baseline_replay_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/android/core/designsystem/src/main/res/drawable/baseline_search_24.xml b/android/core/designsystem/src/main/res/drawable/baseline_search_24.xml
new file mode 100644
index 00000000..a5687c63
--- /dev/null
+++ b/android/core/designsystem/src/main/res/drawable/baseline_search_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/android/core/designsystem/src/main/res/drawable/beer.png b/android/core/designsystem/src/main/res/drawable/beer.png
new file mode 100644
index 00000000..d0989768
Binary files /dev/null and b/android/core/designsystem/src/main/res/drawable/beer.png differ
diff --git a/android/core/designsystem/src/main/res/drawable/border_button.xml b/android/core/designsystem/src/main/res/drawable/border_button.xml
new file mode 100644
index 00000000..d8508059
--- /dev/null
+++ b/android/core/designsystem/src/main/res/drawable/border_button.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/core/designsystem/src/main/res/drawable/chip_ripple_color.xml b/android/core/designsystem/src/main/res/drawable/chip_ripple_color.xml
new file mode 100644
index 00000000..c01226ac
--- /dev/null
+++ b/android/core/designsystem/src/main/res/drawable/chip_ripple_color.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/core/designsystem/src/main/res/drawable/cognac.png b/android/core/designsystem/src/main/res/drawable/cognac.png
new file mode 100644
index 00000000..2575a5a5
Binary files /dev/null and b/android/core/designsystem/src/main/res/drawable/cognac.png differ
diff --git a/android/core/designsystem/src/main/res/drawable/fruit_drink.png b/android/core/designsystem/src/main/res/drawable/fruit_drink.png
new file mode 100644
index 00000000..d12fdb44
Binary files /dev/null and b/android/core/designsystem/src/main/res/drawable/fruit_drink.png differ
diff --git a/android/core/designsystem/src/main/res/drawable/home_24.xml b/android/core/designsystem/src/main/res/drawable/home_24.xml
new file mode 100644
index 00000000..5a870f55
--- /dev/null
+++ b/android/core/designsystem/src/main/res/drawable/home_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/android/core/designsystem/src/main/res/drawable/ic_kakao_login.png b/android/core/designsystem/src/main/res/drawable/ic_kakao_login.png
new file mode 100644
index 00000000..e7f49724
Binary files /dev/null and b/android/core/designsystem/src/main/res/drawable/ic_kakao_login.png differ
diff --git a/android/core/designsystem/src/main/res/drawable/icon2.png b/android/core/designsystem/src/main/res/drawable/icon2.png
new file mode 100644
index 00000000..385271a2
Binary files /dev/null and b/android/core/designsystem/src/main/res/drawable/icon2.png differ
diff --git a/android/core/designsystem/src/main/res/drawable/icon3.png b/android/core/designsystem/src/main/res/drawable/icon3.png
new file mode 100644
index 00000000..52a84adc
Binary files /dev/null and b/android/core/designsystem/src/main/res/drawable/icon3.png differ
diff --git a/android/core/designsystem/src/main/res/drawable/img.png b/android/core/designsystem/src/main/res/drawable/img.png
new file mode 100644
index 00000000..2a742558
Binary files /dev/null and b/android/core/designsystem/src/main/res/drawable/img.png differ
diff --git a/android/core/designsystem/src/main/res/drawable/liquor.png b/android/core/designsystem/src/main/res/drawable/liquor.png
new file mode 100644
index 00000000..aa3bf4dd
Binary files /dev/null and b/android/core/designsystem/src/main/res/drawable/liquor.png differ
diff --git a/android/core/designsystem/src/main/res/drawable/makgeolli.png b/android/core/designsystem/src/main/res/drawable/makgeolli.png
new file mode 100644
index 00000000..4b256fa4
Binary files /dev/null and b/android/core/designsystem/src/main/res/drawable/makgeolli.png differ
diff --git a/android/core/designsystem/src/main/res/drawable/neutral.png b/android/core/designsystem/src/main/res/drawable/neutral.png
new file mode 100644
index 00000000..0cbd736f
Binary files /dev/null and b/android/core/designsystem/src/main/res/drawable/neutral.png differ
diff --git a/android/core/designsystem/src/main/res/drawable/rounded_background.xml b/android/core/designsystem/src/main/res/drawable/rounded_background.xml
new file mode 100644
index 00000000..de5f3858
--- /dev/null
+++ b/android/core/designsystem/src/main/res/drawable/rounded_background.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/android/core/designsystem/src/main/res/drawable/rounded_button_background.xml b/android/core/designsystem/src/main/res/drawable/rounded_button_background.xml
new file mode 100644
index 00000000..2fe22eac
--- /dev/null
+++ b/android/core/designsystem/src/main/res/drawable/rounded_button_background.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/android/core/designsystem/src/main/res/drawable/rounded_edit_text_background.xml b/android/core/designsystem/src/main/res/drawable/rounded_edit_text_background.xml
new file mode 100644
index 00000000..85687275
--- /dev/null
+++ b/android/core/designsystem/src/main/res/drawable/rounded_edit_text_background.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/android/core/designsystem/src/main/res/drawable/sad.png b/android/core/designsystem/src/main/res/drawable/sad.png
new file mode 100644
index 00000000..f79b7257
Binary files /dev/null and b/android/core/designsystem/src/main/res/drawable/sad.png differ
diff --git a/android/core/designsystem/src/main/res/drawable/sake.png b/android/core/designsystem/src/main/res/drawable/sake.png
new file mode 100644
index 00000000..e3da7462
Binary files /dev/null and b/android/core/designsystem/src/main/res/drawable/sake.png differ
diff --git a/android/core/designsystem/src/main/res/drawable/shape_kakao_login.xml b/android/core/designsystem/src/main/res/drawable/shape_kakao_login.xml
new file mode 100644
index 00000000..f1258c09
--- /dev/null
+++ b/android/core/designsystem/src/main/res/drawable/shape_kakao_login.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/android/core/designsystem/src/main/res/drawable/smile.png b/android/core/designsystem/src/main/res/drawable/smile.png
new file mode 100644
index 00000000..df2f9eeb
Binary files /dev/null and b/android/core/designsystem/src/main/res/drawable/smile.png differ
diff --git a/android/core/designsystem/src/main/res/drawable/soju.png b/android/core/designsystem/src/main/res/drawable/soju.png
new file mode 100644
index 00000000..f82ed6ca
Binary files /dev/null and b/android/core/designsystem/src/main/res/drawable/soju.png differ
diff --git a/android/core/designsystem/src/main/res/drawable/star.png b/android/core/designsystem/src/main/res/drawable/star.png
new file mode 100644
index 00000000..b203dc72
Binary files /dev/null and b/android/core/designsystem/src/main/res/drawable/star.png differ
diff --git a/android/core/designsystem/src/main/res/drawable/whiskey.png b/android/core/designsystem/src/main/res/drawable/whiskey.png
new file mode 100644
index 00000000..553f2013
Binary files /dev/null and b/android/core/designsystem/src/main/res/drawable/whiskey.png differ
diff --git a/android/core/designsystem/src/main/res/drawable/wine.png b/android/core/designsystem/src/main/res/drawable/wine.png
new file mode 100644
index 00000000..596c83e9
Binary files /dev/null and b/android/core/designsystem/src/main/res/drawable/wine.png differ
diff --git a/android/core/designsystem/src/main/res/drawable/yakju.png b/android/core/designsystem/src/main/res/drawable/yakju.png
new file mode 100644
index 00000000..33e67eea
Binary files /dev/null and b/android/core/designsystem/src/main/res/drawable/yakju.png differ
diff --git a/android/core/designsystem/src/main/res/font/cookierun_bold.ttf b/android/core/designsystem/src/main/res/font/cookierun_bold.ttf
new file mode 100755
index 00000000..e37c6aa9
Binary files /dev/null and b/android/core/designsystem/src/main/res/font/cookierun_bold.ttf differ
diff --git a/android/core/designsystem/src/main/res/raw/loading.json b/android/core/designsystem/src/main/res/raw/loading.json
new file mode 100644
index 00000000..f4e5ea15
--- /dev/null
+++ b/android/core/designsystem/src/main/res/raw/loading.json
@@ -0,0 +1 @@
+{"v":"5.7.1","fr":30,"ip":0,"op":41,"w":800,"h":600,"nm":"Final","ddd":0,"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":2,"ty":4,"nm":"Colours","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[1554.885,242.795,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":11,"s":[1554.885,242.795,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":23,"s":[1554.885,242.795,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":34,"s":[1554.885,242.795,0],"to":[0,0,0],"ti":[0,0,0]},{"t":45,"s":[1554.885,242.795,0]}],"ix":2},"a":{"a":0,"k":[46.442,99.057,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[25.511,0],[0,0],[0,25.511],[-25.512,0],[0,-25.512]],"o":[[0,0],[-25.512,0],[0,-25.512],[25.511,0],[0,25.511]],"v":[[0,46.192],[0,46.192],[-46.192,0],[0,-46.192],[46.192,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.541000007181,0.067000003889,0.231000010173,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[46.442,151.672],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[25.511,0],[0,0],[0,25.511],[-25.512,0],[0,-25.512]],"o":[[0,0],[-25.512,0],[0,-25.512],[25.511,0],[0,25.511]],"v":[[0,46.192],[0,46.192],[-46.192,0.001],[0,-46.192],[46.192,0.001]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.855000019073,0,0.234999999404,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[46.442,46.443],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1800,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Wine/Wine_Glass","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[5]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10.25,"s":[-5]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20.5,"s":[5]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30.75,"s":[-5]},{"t":41,"s":[5]}],"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[1586]},{"i":{"x":[1],"y":[0.911]},"o":{"x":[0.167],"y":[0.167]},"t":41,"s":[801]},{"t":1799,"s":[771]}],"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[636]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10.25,"s":[505.312]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20.5,"s":[595.459]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30.75,"s":[543.637]},{"t":41,"s":[566]}],"ix":4}},"a":{"a":0,"k":[809,221.5,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0]],"o":[[0,0]],"v":[[319.116,0.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0]],"o":[[0,0]],"v":[[723.402,0.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[28.483,-0.003],[77.802,0],[28.476,21.721],[28.479,0],[0,0],[0,0],[0,0],[0,0],[85.804,1.504],[37.3,21.183],[28.478,0],[0,0],[0,0],[0,0],[0,0],[77.802,0],[28.475,21.721],[28.479,0],[0,0],[0,0],[0,0],[0,0],[77.802,0],[28.476,21.721],[0,0],[0,0],[-28.484,0],[-77.802,-0.004],[-28.476,-21.721],[-28.484,0],[-77.803,-0.004],[-28.475,-21.721],[-28.484,0],[-77.802,-0.004],[-28.475,-21.721],[-28.483,0],[-77.803,-0.005],[-28.476,-21.721],[0.001,56.696]],"o":[[-77.802,0.008],[-28.476,0],[-28.478,-21.723],[0,0],[0,0],[0,0],[0,0],[-34.898,-2.263],[-37.219,-0.652],[-31.145,-17.688],[0,0],[0,0],[0,0],[0,0],[-77.803,0.008],[-28.475,0],[-28.479,-21.723],[0,0],[0,0],[0,0],[0,0],[-77.802,0.008],[-28.476,0],[0,0],[0,0],[28.484,21.726],[77.802,0],[28.476,0.002],[28.483,21.726],[77.803,0],[28.475,0.002],[28.484,21.726],[77.803,0],[28.476,0.002],[28.484,21.726],[77.803,0],[28.476,0.002],[0,-56.697],[-28.484,-21.727]],"v":[[723.135,-220.738],[489.721,-161.39],[404.285,-191.064],[318.865,-220.738],[318.865,-220.238],[318.858,-220.238],[318.865,-220.738],[260.934,-215.489],[64.34,-161.552],[-29.848,-190.199],[-85.42,-220.738],[-85.42,-220.238],[-85.428,-220.238],[-85.42,-220.738],[-85.436,-220.738],[-318.851,-161.39],[-404.286,-191.064],[-489.706,-220.738],[-489.706,-220.238],[-489.714,-220.238],[-489.706,-220.738],[-489.722,-220.738],[-723.136,-161.39],[-816.803,-193.355],[-859.988,-181.553],[-808.572,191.068],[-723.136,220.741],[-489.722,161.394],[-404.286,191.068],[-318.851,220.741],[-85.436,161.394],[-0.001,191.068],[85.435,220.741],[318.849,161.394],[404.285,191.068],[489.72,220.741],[723.135,161.394],[808.571,191.068],[808.571,-191.064]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.854902020623,0,0.235294132607,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[808.822,221.238],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 3","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0]],"o":[[0,0]],"v":[[1127.688,0.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1800,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Wine/Wine_Glass 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[-3.4]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10.25,"s":[3.4]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20.5,"s":[-3.4]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30.75,"s":[3.4]},{"t":41,"s":[-3.4]}],"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[628]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10.25,"s":[833.998]},{"i":{"x":[1],"y":[0.996]},"o":{"x":[0.167],"y":[0.167]},"t":41,"s":[1427.925]},{"t":1799,"s":[771]}],"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[567]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10.25,"s":[550.3]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20.5,"s":[543.372]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30.75,"s":[602.453]},{"t":41,"s":[521]}],"ix":4}},"a":{"a":0,"k":[809,221.5,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0]],"o":[[0,0]],"v":[[319.116,0.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0]],"o":[[0,0]],"v":[[723.402,0.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[28.483,-0.003],[77.802,0],[28.476,21.721],[28.479,0],[0,0],[0,0],[0,0],[0,0],[48.835,2.43],[28.476,21.721],[28.478,0],[0,0],[0,0],[0,0],[0,0],[77.802,0],[21.259,17.385],[28.479,0],[0,0],[0,0],[0,0],[0,0],[66.384,-1.095],[28.476,21.721],[0,0],[0,0],[-28.484,0],[-77.802,-0.004],[-28.476,-21.721],[-28.484,0],[-77.803,-0.004],[-28.475,-21.721],[-28.484,0],[-77.802,-0.004],[-28.475,-21.721],[-28.483,0],[-77.803,-0.005],[-28.476,-21.721],[0.001,56.696]],"o":[[-77.802,0.008],[-28.476,0],[-28.478,-21.723],[0,0],[0,0],[0,0],[0,0],[-78.254,1.717],[-34.459,-1.715],[-28.478,-21.723],[0,0],[0,0],[0,0],[0,0],[-77.803,0.008],[-28.475,0],[-36.571,-29.906],[0,0],[0,0],[0,0],[0,0],[-88.954,0.282],[-28.472,0.469],[0,0],[0,0],[28.484,21.726],[77.802,0],[28.476,0.002],[28.483,21.726],[77.803,0],[28.475,0.002],[28.484,21.726],[77.803,0],[28.476,0.002],[28.484,21.726],[77.803,0],[28.476,0.002],[0,-56.697],[-28.484,-21.727]],"v":[[723.135,-220.738],[489.721,-161.39],[404.285,-191.064],[318.865,-220.738],[318.865,-220.238],[318.858,-220.238],[318.865,-220.738],[305.146,-221.036],[85.435,-161.39],[-4.077,-193.96],[-85.42,-220.738],[-85.42,-220.238],[-85.428,-220.238],[-85.42,-220.738],[-85.436,-220.738],[-313.939,-164.755],[-401.686,-194.007],[-489.706,-220.738],[-489.706,-220.238],[-489.714,-220.238],[-489.706,-220.738],[-489.722,-220.738],[-723.136,-161.39],[-816.803,-193.355],[-859.988,-181.553],[-808.572,191.068],[-723.136,220.741],[-489.722,161.394],[-404.286,191.068],[-318.851,220.741],[-85.436,161.394],[-0.001,191.068],[85.435,220.741],[318.849,161.394],[404.285,191.068],[489.72,220.741],[723.135,161.394],[808.571,191.068],[808.571,-191.064]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.541176470588,0.066666666667,0.231372563979,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[808.822,221.238],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 3","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0]],"o":[[0,0]],"v":[[1127.688,0.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1800,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Master","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[400,268,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[71,71,100],"ix":6}},"ao":0,"ip":0,"op":1800,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Glass_F_01","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[175.221,193.598,0],"ix":2},"a":{"a":0,"k":[186.755,198.348,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[62.243,-72.659]],"o":[[0,0],[0,0]],"v":[[-28.351,-108.594],[-31.121,108.594]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.898039281368,0.870588302612,0.886274576187,1],"ix":3},"o":{"a":0,"k":41,"ix":4},"w":{"a":0,"k":8,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[305.003,211.099],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-19.439],[61.176,0],[0,19.439],[-61.176,0]],"o":[[0,19.439],[-61.176,0],[0,-19.439],[61.176,0]],"v":[[110.769,0],[0,35.198],[-110.769,0],[0,-35.198]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":40,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[186.754,40.198],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[186.51,0.78],[-3.771,10.65],[-32.24,0],[-20.24,6.98]],"o":[[-186.5,0.78],[20.239,6.98],[32.239,0],[3.769,10.65]],"v":[[-0.005,149.789],[-81.505,-150.57],[-0.005,-139.21],[81.505,-150.57]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":16,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[186.755,214.607],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 3","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[56.734,0],[6.517,-17.465],[0,0],[-21.683,-79.121],[-83.35,-2.438],[-3.259,0.019],[0,0],[-3.35,0.089],[-14.691,53.611]],"o":[[0,0],[-6.495,-17.473],[-56.716,0],[0,0],[0,0],[14.692,53.61],[3.129,0.084],[0,0],[3.5,0.03],[83.35,-2.439],[21.684,-79.121]],"v":[[110.006,-162.322],[109.98,-162.322],[-0.012,-193.348],[-109.998,-162.338],[-110.007,-162.338],[-153.298,75.043],[-9.936,193.202],[-0.345,193.302],[-0.345,193.318],[9.936,193.219],[153.298,75.058]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":41,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[186.766,198.349],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[186.51,0.78],[-3.771,10.65],[-32.24,0],[-20.24,6.98]],"o":[[-186.5,0.78],[20.239,6.98],[32.239,0],[3.769,10.65]],"v":[[-0.005,166.049],[-81.505,-134.31],[-0.005,-122.951],[81.505,-134.31]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[56.739,0],[6.52,-17.46],[0,0],[-21.691,-79.119],[-83.35,-2.429],[-3.261,0.021],[0,0],[-3.349,0.09],[-14.691,53.61]],"o":[[0,0],[-6.5,-17.469],[-56.71,0],[0,0],[0,0],[14.69,53.62],[3.131,0.09],[0,0],[3.5,0.03],[83.351,-2.441],[21.69,-79.119]],"v":[[110.015,-162.321],[109.995,-162.321],[-0.005,-193.35],[-109.985,-162.341],[-109.995,-162.341],[-153.285,75.039],[-9.926,193.199],[-0.335,193.299],[-0.335,193.32],[9.944,193.22],[153.305,75.059]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":10,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[186.755,198.347],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 5","np":4,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1800,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Wine_Alpha_Matte","parent":5,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[175.232,193.598,0],"ix":2},"a":{"a":0,"k":[179.982,198.348,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[56.734,0],[6.517,-17.465],[0,0],[-21.683,-79.121],[-83.35,-2.438],[-3.259,0.019],[0,0],[-3.35,0.089],[-14.691,53.611]],"o":[[0,0],[-6.495,-17.473],[-56.716,0],[0,0],[0,0],[14.692,53.61],[3.129,0.084],[0,0],[3.5,0.03],[83.35,-2.439],[21.684,-79.121]],"v":[[110.006,-162.322],[109.981,-162.322],[-0.011,-193.348],[-109.998,-162.338],[-110.006,-162.338],[-153.298,75.043],[-9.936,193.202],[-0.344,193.302],[-0.344,193.318],[9.936,193.219],[153.298,75.058]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.670999983245,0.426999978458,0.505999995213,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.349000010771,0.172999991623,0.282000014361,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[179.981,198.348],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1800,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"Wine_Layer_01","parent":1,"tt":1,"refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2},"a":{"a":0,"k":[960,540,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"w":1920,"h":1080,"ip":0,"op":1800,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Glass_B_01","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[-5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":11,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":21,"s":[-5]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":31,"s":[5]},{"t":40,"s":[-5]}],"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[-0.754]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[33.246]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":20,"s":[-0.754]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":30,"s":[33.246]},{"t":40,"s":[-0.754]}],"ix":3},"y":{"a":0,"k":173.522,"ix":4}},"a":{"a":0,"k":[175.231,477.398,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[56.734,0],[6.517,-17.465],[0,0],[-21.683,-79.121],[-83.35,-2.438],[-3.259,0.019],[0,0],[-3.35,0.089],[-14.691,53.611]],"o":[[0,0],[-6.495,-17.473],[-56.716,0],[0,0],[0,0],[14.692,53.61],[3.129,0.084],[0,0],[3.5,0.03],[83.35,-2.439],[21.684,-79.121]],"v":[[110.006,-162.322],[109.981,-162.322],[-0.011,-193.348],[-109.998,-162.338],[-110.006,-162.338],[-153.298,75.043],[-9.936,193.202],[-0.344,193.302],[-0.344,193.318],[9.936,193.219],[153.298,75.058]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.380000005984,0.211999990426,0.310000011968,1],"ix":4},"o":{"a":0,"k":20,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[175.231,193.598],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[3.5,0.03],[3.349,0.09],[0,0],[4.719,-6.067],[-19.121,-6.198],[-7.516,0],[0,0],[12,15.428],[0,0],[0,0]],"o":[[-3.5,0.03],[0,0],[0,0],[-12,15.428],[0,0],[7.517,0],[19.121,-6.198],[-4.718,-6.067],[0,0],[-3.349,0.09]],"v":[[0.001,-135.178],[-10.28,-135.278],[-10.28,94.301],[-14.74,105.376],[-19.751,132.936],[-0.134,135.278],[19.751,132.936],[14.739,105.376],[10.28,94.301],[10.28,-135.278]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.670999983245,0.426999978458,0.505999995213,1],"ix":4},"o":{"a":0,"k":60,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[175.22,522.094],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-22.74],[64.179,0],[0,22.74],[-64.18,0]],"o":[[0,22.74],[-64.18,0],[0,-22.74],[64.179,0]],"v":[[116.208,0],[0.001,41.174],[-116.208,0],[0.001,-41.174]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.579999976065,0.352999997606,0.426999978458,1],"ix":4},"o":{"a":0,"k":60,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[175.22,657.372],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 3","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1800,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":1,"nm":"White Solid 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[400,300,0],"ix":2},"a":{"a":0,"k":[400,300,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"sw":800,"sh":600,"sc":"#ffffff","ip":0,"op":1800,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/android/core/designsystem/src/main/res/values-night/themes.xml b/android/core/designsystem/src/main/res/values-night/themes.xml
new file mode 100644
index 00000000..df45d6aa
--- /dev/null
+++ b/android/core/designsystem/src/main/res/values-night/themes.xml
@@ -0,0 +1,14 @@
+
+
+
+
\ No newline at end of file
diff --git a/android/core/designsystem/src/main/res/values/colors.xml b/android/core/designsystem/src/main/res/values/colors.xml
new file mode 100644
index 00000000..19c19ebc
--- /dev/null
+++ b/android/core/designsystem/src/main/res/values/colors.xml
@@ -0,0 +1,16 @@
+
+
+ #FF000000
+ #FFFFFFFF
+ #FFF7F7FA
+ #FFCCCCCC
+ #FFDCDCDC
+ #00000000
+ #FF9932CC
+ #FFEBE8FC
+ #FFBB86FC
+ #FF6200EE
+ #FF3700B3
+ #FF03DAC5
+ #FFECC440
+
\ No newline at end of file
diff --git a/android/core/designsystem/src/main/res/values/strings.xml b/android/core/designsystem/src/main/res/values/strings.xml
new file mode 100644
index 00000000..86ffa94e
--- /dev/null
+++ b/android/core/designsystem/src/main/res/values/strings.xml
@@ -0,0 +1,106 @@
+
+
+ ๊ฐ์ ๋ถ์์ ์ฌ์ฉํ ์ฌ์ง์ ์ ํํด์ฃผ์ธ์.
+ ์จ๋ฒ์์ ๊ฐ์ ธ์ค๊ธฐ
+ ์นด๋ฉ๋ผ๋ก ์ดฌ์ํ๊ธฐ
+ ๋ถ์ํ๊ธฐ
+ ์ค๋์ ์ด๋ค ์ผ์ด ์์๋์?
+ ๊ฐ์ ์ด ๋ค์ด๊ฐ ๋ฌธ์ฅ์ด ํฌํจ๋ ์๋ก ๋ ์ข์์!
+ ๋ค ์์ฑํ์ด์!
+ ๋น์ฃผ๋ฅ์์ ๋น์ ์ ํ๋ฃจ์ ์ด์ธ๋ฆฌ๋\n์ฃผ๋ฅ๋ฅผ ์ถ์ฒ ๋ฐ์๋ณด์ธ์.
+ ๊ณ์ ๊ด๋ฆฌ
+ ํ๋กํ ๊ด๋ฆฌ
+ ์ค์
+ ์ด์ฉ ์ฝ๊ด
+ ์ฝ๊ด ๋ฐ ์ ์ฑ
+ ๋๋ค์
+ ๋ก๊ทธ์์
+ ํ์ ํํด
+ ๊ฐ์ธ ์ ๋ณด์ ๊ธฐ๋ก์ด ๋ชจ๋ ์ญ์ ๋ฉ๋๋ค.
+ ๋ณ๊ฒฝํ ๋๋ค์์ ์
๋ ฅํ์ธ์.
+ ์ค๋์ ๊ฐ์ ์์ฑ
+ ์๋ฃ
+ ํ์ธ
+ ์๋์
+ ์ด์ฉ ๊ธฐ๋ก
+ ์ํ๋ ์ฃผ๋ฅ๋ฅผ ๊ฒ์ํ์ธ์!
+ ๋งฅ์ฃผ
+ ์์ฃผ
+ ์์ธ
+ ์ฒญ์ฃผ
+ ์์คํค
+ ๊ณผ์ค์ฃผ
+ ์ฝ์ฃผ
+ ๋ง๊ฑธ๋ฆฌ
+ ๊ผฌ๋
+ ์ฌ์ผ
+ ๋ฆฌํ๋ฅด
+ ์ค๋ ํ๋ฃจ๋ ์ด๋ ์
จ๋์?\n์ค๋ ๊ธฐ๋ถ์ ๋ง๋ ์ฃผ๋ฅ๋ฅผ ์ถ์ฒํด๋๋ฆด๊ฒ์.
+ Be์ฃผ๋ฅ
+ ์ต๊ทผ ๊ฒ์์ด
+ ๋น์ ์ ํ๋ฃจ๋ฅผ ๋ถ์ํด, ์๋ง๋ ์ฃผ๋ฅ๋ฅผ ์ถ์ฒํ ๊ฒ์.
+ ๋์
+ ์ฉ๋
+ ์ข
๋ฅ
+ ๊ฐ๊ฒฉ
+ ๋น๋(100ml๋น)
+ ๋ฆฌ๋ทฐ ํ์ธํ๊ธฐ
+ ๋ฆฌ๋ทฐ ์์ฑํ๊ธฐ
+ ์ฃผ๋ฅ ์ ๋ณด
+ %.1f/5.0
+ ๊ตฌ๋งคํ๊ธฐ
+ %1$s ๋ฆฌ๋ทฐ
+ ํ์
+ ์ฃผ๋ฅ ํ๊ธฐ ์์ฑ
+ ์ฃผ๋ฅ | %1$s
+ ๋ด์ฉ
+ ์ฃผ๋ฅ์ ๋ํ ํ๊ธฐ๋ฅผ ์์ฑํด ์ฃผ์ธ์.
+ ๋ฑ๋ก
+ ์ฃผ๋ฅ์ ๋ํ ํ๊ธฐ๋ฅผ ๋จ๊ฒจ์ฃผ์ธ์
+ %1$s๋
+ ๊ฐ์ ๋ถ์ ๊ฒฐ๊ณผ
+ %1$s๋์ ๊ฐ์ ๋ถ์ ๊ฒฐ๊ณผ๋ %2$s์
๋๋ค.
+ ์ฃผ๋ฅ ์ถ์ฒ ๊ฒฐ๊ณผ
+ ์ผ๊ตด ํ์ ์ด ๋ค์ด๋ ์ฌ์ง์ ๋ ์ ๋ถ์ํด์!
+ ์ฌ์ง์ ํด๋ฆญํ์ฌ ์ด๋ฏธ์ง ์ ํํ๊ธฐ
+ ์ฌ์ง ์
๋ ฅ
+ %.1f%%
+ %d์
+ %dml
+ Be์ฃผ๋ฅ |
+ Be์ฃผ๋ฅ ๋ญํน
+ %dg
+ ์ ์ ๋ค์ด ์ง์ ์ ์ ํ \n๋น์ฃผ๋ฅ์์ ๊ฐ์ฅ ์ธ๊ธฐ๊ฐ ๋ง์ ์ ์ ํ์ธํด๋ณด์ธ์!
+ ์นดํ
๊ณ ๋ฆฌ
+ /5
+ 0/10์ ์ด์
+ %1$s
+ ๋ถ์ ๊ฐ์ : %1$s
+ ๋ฆฌ๋ทฐ ๋ง์ ์
+ ํ์ ๋์ ์
+ %1$s๋\n์ค๋์ ๊ธฐ๋ถ์ ์ด๋ ์ ๊ฐ์?
+ ๋ค์ ์๋ํ๊ธฐ.
+ ์กฐ๊ธ๋ง ๊ธฐ๋ค๋ ค์ฃผ์ธ์. \n์ค๋์ ๊ธฐ๋ถ์ ์๋ง๋ ์ ์ ์ถ์ฒํด๋๋ฆด๊ฒ์!
+ ์ฃผ๋ฅ ๊ฒ์
+ ๊ถ๊ธํ ์ฃผ๋ฅ์ ๋ฆฌ๋ทฐ๋ฅผ ํ์ธํด๋ณด์ธ์!
+ %.1f (%d)
+ ๋ณ์ ๋ฑ๋ก
+ ๊ฒ์๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค!\n๋ค๋ฅธ ํค์๋๋ฅผ ์
๋ ฅํด์ฃผ์ธ์.
+ ๋ค์ ๊ฒ์ํ๊ธฐ
+ ๋ฆฌ๋ทฐ ์์ฑํ๊ธฐ
+ ๋ฑ๋ก๋ ๋ฆฌ๋ทฐ๊ฐ ์์ต๋๋ค.\n๊ฐ์ฅ ๋จผ์ ๋ฆฌ๋ทฐ๋ฅผ ์์ฑํด๋ณด์ธ์!
+ "1. ๋ช
ํํ๊ณ ๊ฐ์ ์ด ํํ๋ ๋จ์ด๋ฅผ ํ์ฉํ๊ธฐ\n- โ๊ธฐ์๋คโ, โ์ฌํ๋คโ, โํ๋๋คโ์ ๊ฐ์ ๋ช
ํํ ๊ฐ์ ํํ์ ์ฌ์ฉํ๋ฉด ๋ ์ข์์!\n\n2. ๊ฐ๊ฒฐํ๊ณ ๋ช
ํํ ๋ฌธ์ฅ์ ๊ตฌ์ฑํ๊ธฐ\n- ์งง๊ณ ๊ฐ๊ฒฐํ ๋ฌธ์ฅ์ ์ฌ์ฉํ๋ฉด ๋ ์ข์์!\n\n3. ๋ํ์ฒด๋ ๊ฐ์ ์ ๊ฐ์กฐํ๋ ์ด๊ตฌ ์ฌ์ฉํ๊ธฐ\n- ์ง์ ๋ค๋ฅธ ์ฌ๋์๊ฒ ๋งํ๋ ๊ฒ์ฒ๋ผ ์์ฑํ๋ฉด ๋ ์ข์์!"
+
+ ๊ฐ์ ์์ฑ Tip
+ ์ด๋ ๊ฒ ์์ฑํ ์๋ก ์ธ๊ณต์ง๋ฅ์ด ๋ ์ ์ดํดํด์!
+ ๊ฐ์ ๋ถ์ ์ฌ์ง ์
๋ ฅ
+ "1. ์ด๋ชฉ๊ตฌ๋น๊ฐ ๋ค ๋ค์ด๋ ์ฌ์ง\n- ๊ฐ ๋ถ์์ ๋ชจ์๊ณผ ์์น๋ฅผ ๋ณด๊ณ ๊ฐ์ ์ ํ๋จํ๊ธฐ ๋๋ฌธ์, ๋ค ๋ค์ด๋ ์ฌ์ง์ด ๋ ์ข์์!\n\n2. ์์ธ์ ๋์์ด ํํ๋ ์ฌ์ง\n- ์์ธ์ ๋์๋ ๊ฐ์ ์ ์ ๋ฌํ ์ ์๊ธฐ ๋๋ฌธ์, ํํ๋ ์ฌ์ง์ด ๋ ์ข์์!\n\n3. ๊ฐ์ ์ ๋ํ๋ด๋ ๋ฐฐ๊ฒฝ์ด ๋ค์ด๋ ์ฌ์ง\n- ์ผ๊ตด์ด ํํ๋ ์ฌ์ง์ด ์ด๋ ค์ฐ๋ฉด, ๋ฐฐ๊ฒฝ ์ฌ์ง๋ ๊ด์ฐฎ์์!"
+
+ ๊ฐ์ ๋ถ์
+ ์ฌ์ง ์
๋ ฅ Tip
+ ์ด๋ฐ ์ฌ์ง์ผ ์๋ก ์ธ๊ณต์ง๋ฅ์ด ๋ ์ ์ดํดํด์!
+ %s(%dml)
+ %d๊ฐ์ ๋ฆฌ๋ทฐ
+ ์
๋ ฅ ์ ๋ณด
+ ์ฃผ๋ฅ ์ถ์ฒ ๊ฒฐ๊ณผ"
+
\ No newline at end of file
diff --git a/android/core/designsystem/src/main/res/values/themes.xml b/android/core/designsystem/src/main/res/values/themes.xml
new file mode 100644
index 00000000..df45d6aa
--- /dev/null
+++ b/android/core/designsystem/src/main/res/values/themes.xml
@@ -0,0 +1,14 @@
+
+
+
+
\ No newline at end of file
diff --git a/android/core/designsystem/src/test/java/com/jaino/designsystem/ExampleUnitTest.kt b/android/core/designsystem/src/test/java/com/jaino/designsystem/ExampleUnitTest.kt
new file mode 100644
index 00000000..eb228e71
--- /dev/null
+++ b/android/core/designsystem/src/test/java/com/jaino/designsystem/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.jaino.designsystem
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * 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)
+ }
+}
\ No newline at end of file
diff --git a/android/core/domain/.gitignore b/android/core/domain/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/android/core/domain/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/android/core/domain/build.gradle.kts b/android/core/domain/build.gradle.kts
new file mode 100644
index 00000000..38d04e5e
--- /dev/null
+++ b/android/core/domain/build.gradle.kts
@@ -0,0 +1,15 @@
+plugins {
+ id("com.jaino.feature")
+ id("com.jaino.serialization")
+ id("com.jaino.hilt")
+}
+
+android {
+ namespace = "com.jaino.domain"
+}
+
+dependencies {
+ implementation(project(":core:data"))
+ implementation(project(":core:model"))
+ implementation(libs.bundles.kotlin)
+}
\ No newline at end of file
diff --git a/android/core/domain/consumer-rules.pro b/android/core/domain/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/android/core/domain/proguard-rules.pro b/android/core/domain/proguard-rules.pro
new file mode 100644
index 00000000..ff59496d
--- /dev/null
+++ b/android/core/domain/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.kts.
+#
+# 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/android/core/domain/src/androidTest/java/com/jaino/domain/ExampleInstrumentedTest.kt b/android/core/domain/src/androidTest/java/com/jaino/domain/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..b552eb18
--- /dev/null
+++ b/android/core/domain/src/androidTest/java/com/jaino/domain/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.jaino.domain
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.jaino.domain.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/android/core/domain/src/main/AndroidManifest.xml b/android/core/domain/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a5918e68
--- /dev/null
+++ b/android/core/domain/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/android/core/domain/src/main/java/model/review/ReviewModel.kt b/android/core/domain/src/main/java/model/review/ReviewModel.kt
new file mode 100644
index 00000000..a2a85677
--- /dev/null
+++ b/android/core/domain/src/main/java/model/review/ReviewModel.kt
@@ -0,0 +1,11 @@
+package model.review
+
+data class ReviewModel (
+ val reviewId : Long,
+ val drinkName : String,
+ val image : String,
+ val nickname : String,
+ val comment : String,
+ val score : Int,
+ val date : String
+)
diff --git a/android/core/domain/src/main/java/model/validate/ValidationResult.kt b/android/core/domain/src/main/java/model/validate/ValidationResult.kt
new file mode 100644
index 00000000..b3369890
--- /dev/null
+++ b/android/core/domain/src/main/java/model/validate/ValidationResult.kt
@@ -0,0 +1,6 @@
+package model.validate
+
+data class ValidationResult(
+ val successful: Boolean,
+ val errorMessage: String? = null
+)
\ No newline at end of file
diff --git a/android/core/domain/src/main/java/usecase/review/GetReviewList.kt b/android/core/domain/src/main/java/usecase/review/GetReviewList.kt
new file mode 100644
index 00000000..948cc1d9
--- /dev/null
+++ b/android/core/domain/src/main/java/usecase/review/GetReviewList.kt
@@ -0,0 +1,35 @@
+package usecase.review
+
+import com.jaino.data.repository.dictionary.DrinksRepository
+import com.jaino.data.repository.review.ReviewRepository
+import model.review.ReviewModel
+import javax.inject.Inject
+
+class GetReviewList @Inject constructor(
+ private val drinksRepository : DrinksRepository,
+ private val reviewRepository: ReviewRepository
+){
+ suspend operator fun invoke(drinkId: Long) : Result>{
+ return runCatching {
+ val reviewItem = mutableListOf()
+ val drinkInfo = drinksRepository.getDrinkDataById(drinkId).getOrThrow()
+ reviewRepository.getReviewList(drinkId).getOrThrow()
+ .forEach{ drinkReview ->
+ reviewItem.add(
+ ReviewModel(
+ reviewId = drinkInfo.id,
+ drinkName = drinkInfo.name,
+ image = drinkInfo.image,
+ nickname = drinkReview.nickname,
+ comment = drinkReview.comment,
+ score = drinkReview.score,
+ date = drinkReview.date
+ )
+ )
+ }
+ reviewItem.toList()
+ }.onFailure {
+ Result.failure(it)
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/core/domain/src/main/java/usecase/validate/ValidateNickname.kt b/android/core/domain/src/main/java/usecase/validate/ValidateNickname.kt
new file mode 100644
index 00000000..79033d9f
--- /dev/null
+++ b/android/core/domain/src/main/java/usecase/validate/ValidateNickname.kt
@@ -0,0 +1,17 @@
+package usecase.validate
+
+import model.validate.ValidationResult
+
+class ValidateNickname {
+ operator fun invoke(nickname: String): ValidationResult {
+ if(nickname.length !in 2..9){
+ return ValidationResult(
+ successful = false,
+ errorMessage = "์ด๋ฆ์ 2๊ธ์ ์ด์ 9๊ธ์ ์ดํ๋ก ์
๋ ฅํด์ฃผ์ธ์."
+ )
+ }
+ return ValidationResult(
+ successful = true
+ )
+ }
+}
\ No newline at end of file
diff --git a/android/core/domain/src/main/java/usecase/validate/ValidateTextExpression.kt b/android/core/domain/src/main/java/usecase/validate/ValidateTextExpression.kt
new file mode 100644
index 00000000..b12b7ce4
--- /dev/null
+++ b/android/core/domain/src/main/java/usecase/validate/ValidateTextExpression.kt
@@ -0,0 +1,24 @@
+package usecase.validate
+
+import model.validate.ValidationResult
+
+class ValidateTextExpression {
+ operator fun invoke(text: String): ValidationResult {
+ if(text.length < 10){
+ return ValidationResult(
+ successful = false,
+ errorMessage = "10๊ธ์ ์ด์์ผ๋ก ์์ฑํด์ฃผ์ธ์!"
+ )
+ }
+ val containsLetters= text.any { it.isLetter() }
+ if(!containsLetters){
+ return ValidationResult(
+ successful = false,
+ errorMessage = "ํ ๊ธ์์ด์ ํฌํจ๋์ด ์์ด์ผ ํฉ๋๋ค!"
+ )
+ }
+ return ValidationResult(
+ successful = true
+ )
+ }
+}
\ No newline at end of file
diff --git a/android/core/domain/src/test/java/com/jaino/domain/ExampleUnitTest.kt b/android/core/domain/src/test/java/com/jaino/domain/ExampleUnitTest.kt
new file mode 100644
index 00000000..a4dd73b1
--- /dev/null
+++ b/android/core/domain/src/test/java/com/jaino/domain/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.jaino.domain
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * 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)
+ }
+}
\ No newline at end of file
diff --git a/android/core/model/.gitignore b/android/core/model/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/android/core/model/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/android/core/model/build.gradle.kts b/android/core/model/build.gradle.kts
new file mode 100644
index 00000000..a02b7040
--- /dev/null
+++ b/android/core/model/build.gradle.kts
@@ -0,0 +1,7 @@
+plugins {
+ id("kotlin")
+}
+
+dependencies {
+ implementation(libs.bundles.kotlin)
+}
\ No newline at end of file
diff --git a/android/core/model/consumer-rules.pro b/android/core/model/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/android/core/model/proguard-rules.pro b/android/core/model/proguard-rules.pro
new file mode 100644
index 00000000..ff59496d
--- /dev/null
+++ b/android/core/model/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.kts.
+#
+# 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/android/core/model/src/androidTest/java/com/jaino/model/ExampleInstrumentedTest.kt b/android/core/model/src/androidTest/java/com/jaino/model/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..bcb57cc2
--- /dev/null
+++ b/android/core/model/src/androidTest/java/com/jaino/model/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.jaino.model
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.jaino.model.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/android/core/model/src/main/AndroidManifest.xml b/android/core/model/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a5918e68
--- /dev/null
+++ b/android/core/model/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/android/core/model/src/main/java/com/jaino/model/analysis/AnalysisHistory.kt b/android/core/model/src/main/java/com/jaino/model/analysis/AnalysisHistory.kt
new file mode 100644
index 00000000..148623ba
--- /dev/null
+++ b/android/core/model/src/main/java/com/jaino/model/analysis/AnalysisHistory.kt
@@ -0,0 +1,7 @@
+package com.jaino.model.analysis
+
+data class AnalysisHistory (
+ val id : Long,
+ val date: String,
+ val sentiment : String
+)
\ No newline at end of file
diff --git a/android/core/model/src/main/java/com/jaino/model/analysis/AnalysisId.kt b/android/core/model/src/main/java/com/jaino/model/analysis/AnalysisId.kt
new file mode 100644
index 00000000..6fdb2a99
--- /dev/null
+++ b/android/core/model/src/main/java/com/jaino/model/analysis/AnalysisId.kt
@@ -0,0 +1,5 @@
+package com.jaino.model.analysis
+
+data class AnalysisId(
+ val analysisId: Long
+)
\ No newline at end of file
diff --git a/android/core/model/src/main/java/com/jaino/model/analysis/SentimentAnalysis.kt b/android/core/model/src/main/java/com/jaino/model/analysis/SentimentAnalysis.kt
new file mode 100644
index 00000000..7a7409bb
--- /dev/null
+++ b/android/core/model/src/main/java/com/jaino/model/analysis/SentimentAnalysis.kt
@@ -0,0 +1,35 @@
+package com.jaino.model.analysis
+
+data class SentimentAnalysis(
+ val id: Long,
+ val textExpression: String,
+ val facialExpression: String,
+ val sentiment: String,
+ val level : Float,
+ val date : String,
+ val drinkId : Long,
+ val name : String,
+ val dosu : Double,
+ val price : Int,
+ val volume : Int,
+ val drinkImage: String,
+ val type : String,
+ val sweetness : Int
+){
+ constructor() : this(
+ 0,
+ "",
+ "",
+ "",
+ 0f,
+ "",
+ 0,
+ "",
+ 0.0,
+ 0,
+ 0,
+ "",
+ "",
+ 0
+ )
+}
\ No newline at end of file
diff --git a/android/core/model/src/main/java/com/jaino/model/auth/User.kt b/android/core/model/src/main/java/com/jaino/model/auth/User.kt
new file mode 100644
index 00000000..0a8fb7cf
--- /dev/null
+++ b/android/core/model/src/main/java/com/jaino/model/auth/User.kt
@@ -0,0 +1,8 @@
+package com.jaino.model.auth
+
+data class User(
+ val accessToken: String,
+ val refreshToken : String,
+ val userId : Long,
+ val nickName : String
+)
\ No newline at end of file
diff --git a/android/core/model/src/main/java/com/jaino/model/dictionary/Drink.kt b/android/core/model/src/main/java/com/jaino/model/dictionary/Drink.kt
new file mode 100644
index 00000000..95fa7fd4
--- /dev/null
+++ b/android/core/model/src/main/java/com/jaino/model/dictionary/Drink.kt
@@ -0,0 +1,27 @@
+package com.jaino.model.dictionary
+
+data class Drink(
+ val id : Long,
+ val name : String,
+ val dosu : Double,
+ val sweetness : Int,
+ val price : Int,
+ val volume : Int,
+ val image : String,
+ val type : String,
+ val rating : Float,
+ val reviewCount: Int
+){
+ constructor(): this(
+ 0,
+ "",
+ 0.0,
+ 0,
+ 0,
+ 0,
+ "",
+ "",
+ 0.0f,
+ 0
+ )
+}
\ No newline at end of file
diff --git a/android/core/model/src/main/java/com/jaino/model/rank/Rank.kt b/android/core/model/src/main/java/com/jaino/model/rank/Rank.kt
new file mode 100644
index 00000000..2fe2d20c
--- /dev/null
+++ b/android/core/model/src/main/java/com/jaino/model/rank/Rank.kt
@@ -0,0 +1,10 @@
+package com.jaino.model.rank
+
+data class Rank(
+ val id : Long,
+ val name : String,
+ val type : String,
+ val rating : Double,
+ val reviewCount : Int,
+ val image : String,
+)
\ No newline at end of file
diff --git a/android/core/model/src/main/java/com/jaino/model/review/Review.kt b/android/core/model/src/main/java/com/jaino/model/review/Review.kt
new file mode 100644
index 00000000..f50ceefe
--- /dev/null
+++ b/android/core/model/src/main/java/com/jaino/model/review/Review.kt
@@ -0,0 +1,9 @@
+package com.jaino.model.review
+
+data class Review(
+ val reviewId : Long,
+ val comment : String,
+ val score : Int,
+ val date : String,
+ val nickname : String
+)
\ No newline at end of file
diff --git a/android/core/model/src/main/java/com/jaino/model/setting/Profile.kt b/android/core/model/src/main/java/com/jaino/model/setting/Profile.kt
new file mode 100644
index 00000000..8834b2b9
--- /dev/null
+++ b/android/core/model/src/main/java/com/jaino/model/setting/Profile.kt
@@ -0,0 +1,6 @@
+package com.jaino.model.setting
+
+data class Profile(
+ val userId: Long,
+ val nickname: String
+)
\ No newline at end of file
diff --git a/android/core/model/src/test/java/com/jaino/model/ExampleUnitTest.kt b/android/core/model/src/test/java/com/jaino/model/ExampleUnitTest.kt
new file mode 100644
index 00000000..7a365a24
--- /dev/null
+++ b/android/core/model/src/test/java/com/jaino/model/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.jaino.model
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * 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)
+ }
+}
\ No newline at end of file
diff --git a/android/core/network/.gitignore b/android/core/network/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/android/core/network/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/android/core/network/build.gradle.kts b/android/core/network/build.gradle.kts
new file mode 100644
index 00000000..aae02df0
--- /dev/null
+++ b/android/core/network/build.gradle.kts
@@ -0,0 +1,35 @@
+plugins {
+ id("com.jaino.feature")
+ id("com.jaino.serialization")
+ id("com.jaino.hilt")
+}
+
+android {
+ defaultConfig {
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+
+ buildConfigField("String", "BASE_DOMAIN_URL", getLocalProperty("BASE_DOMAIN_URL"))
+ }
+ namespace = "com.jaino.network"
+}
+
+fun getLocalProperty(property: String): String {
+ return com.android.build.gradle.internal.cxx.configure.gradleLocalProperties(rootDir).getProperty(property)
+}
+
+dependencies {
+ implementation(project(":core:model"))
+ implementation(project(":core:datastore"))
+ implementation(platform(libs.okhttp.bom))
+ implementation(libs.bundles.okhttp)
+ implementation(libs.retrofit)
+ implementation(libs.retrofit.kotlin.serialization.converter)
+ implementation(libs.bundles.retrofit)
+ implementation(libs.bundles.kotlin)
+ implementation(libs.timber)
+ implementation(libs.processphoenix)
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.androidx.test.junit)
+ androidTestImplementation(libs.androidx.test.espresso)
+}
\ No newline at end of file
diff --git a/android/core/network/consumer-rules.pro b/android/core/network/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/android/core/network/proguard-rules.pro b/android/core/network/proguard-rules.pro
new file mode 100644
index 00000000..ff59496d
--- /dev/null
+++ b/android/core/network/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.kts.
+#
+# 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/android/core/network/src/androidTest/java/com/jaino/network/ExampleInstrumentedTest.kt b/android/core/network/src/androidTest/java/com/jaino/network/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..471c9af6
--- /dev/null
+++ b/android/core/network/src/androidTest/java/com/jaino/network/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.jaino.network
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.jaino.network.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/android/core/network/src/main/AndroidManifest.xml b/android/core/network/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a5918e68
--- /dev/null
+++ b/android/core/network/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/android/core/network/src/main/java/com/jaino/network/datasource/analysis/AnalysisDataSource.kt b/android/core/network/src/main/java/com/jaino/network/datasource/analysis/AnalysisDataSource.kt
new file mode 100644
index 00000000..f3fe7da4
--- /dev/null
+++ b/android/core/network/src/main/java/com/jaino/network/datasource/analysis/AnalysisDataSource.kt
@@ -0,0 +1,16 @@
+package com.jaino.network.datasource.analysis
+
+import com.jaino.network.model.request.analysis.AnalysisSourceRequest
+import com.jaino.network.model.response.analysis.AnalysisIdResponse
+import com.jaino.network.model.response.analysis.AnalysisHistoryResponse
+import com.jaino.network.model.response.analysis.SentimentAnalysisResponse
+
+interface AnalysisDataSource {
+ suspend fun postAnalysisSource(
+ analysisSourceRequest: AnalysisSourceRequest
+ ): Result
+
+ suspend fun getAnalysisList() : Result>
+
+ suspend fun getSentimentAnalysis(analysisId: Long) : Result
+}
\ No newline at end of file
diff --git a/android/core/network/src/main/java/com/jaino/network/datasource/analysis/AnalysisDataSourceImpl.kt b/android/core/network/src/main/java/com/jaino/network/datasource/analysis/AnalysisDataSourceImpl.kt
new file mode 100644
index 00000000..467e5ebd
--- /dev/null
+++ b/android/core/network/src/main/java/com/jaino/network/datasource/analysis/AnalysisDataSourceImpl.kt
@@ -0,0 +1,35 @@
+package com.jaino.network.datasource.analysis
+
+import com.jaino.network.model.request.analysis.AnalysisSourceRequest
+import com.jaino.network.model.response.analysis.AnalysisIdResponse
+import com.jaino.network.model.response.analysis.AnalysisHistoryResponse
+import com.jaino.network.model.response.analysis.SentimentAnalysisResponse
+import com.jaino.network.remote.AnalysisService
+import javax.inject.Inject
+
+class AnalysisDataSourceImpl @Inject constructor(
+ private val service : AnalysisService
+): AnalysisDataSource {
+ override suspend fun postAnalysisSource(
+ analysisSourceRequest: AnalysisSourceRequest
+ ): Result =
+ runCatching {
+ service.postAnalysisSource(analysisSourceRequest)
+ }.onFailure { error ->
+ Result.failure(error)
+ }
+
+ override suspend fun getAnalysisList(): Result> =
+ runCatching {
+ service.getAnalyzeList()
+ }.onFailure { error ->
+ Result.failure>(error)
+ }
+
+ override suspend fun getSentimentAnalysis(analysisId: Long): Result =
+ runCatching {
+ service.getSentimentAnalysis(analysisId)
+ }.onFailure { error ->
+ Result.failure(error)
+ }
+}
\ No newline at end of file
diff --git a/android/core/network/src/main/java/com/jaino/network/datasource/auth/AuthDataSource.kt b/android/core/network/src/main/java/com/jaino/network/datasource/auth/AuthDataSource.kt
new file mode 100644
index 00000000..15061e04
--- /dev/null
+++ b/android/core/network/src/main/java/com/jaino/network/datasource/auth/AuthDataSource.kt
@@ -0,0 +1,8 @@
+package com.jaino.network.datasource.auth
+
+import com.jaino.network.model.response.auth.SignInResponse
+
+
+interface AuthDataSource {
+ suspend fun signIn(token: String) : SignInResponse
+}
\ No newline at end of file
diff --git a/android/core/network/src/main/java/com/jaino/network/datasource/auth/AuthDataSourceImpl.kt b/android/core/network/src/main/java/com/jaino/network/datasource/auth/AuthDataSourceImpl.kt
new file mode 100644
index 00000000..33240f3e
--- /dev/null
+++ b/android/core/network/src/main/java/com/jaino/network/datasource/auth/AuthDataSourceImpl.kt
@@ -0,0 +1,12 @@
+package com.jaino.network.datasource.auth
+
+import com.jaino.network.model.response.auth.SignInResponse
+import com.jaino.network.remote.AuthService
+import javax.inject.Inject
+
+class AuthDataSourceImpl @Inject constructor(
+ private val service : AuthService
+) : AuthDataSource{
+ override suspend fun signIn(token: String) : SignInResponse =
+ service.signIn(token)
+}
\ No newline at end of file
diff --git a/android/core/network/src/main/java/com/jaino/network/datasource/dictionary/DrinkDataSource.kt b/android/core/network/src/main/java/com/jaino/network/datasource/dictionary/DrinkDataSource.kt
new file mode 100644
index 00000000..ba7f6bd0
--- /dev/null
+++ b/android/core/network/src/main/java/com/jaino/network/datasource/dictionary/DrinkDataSource.kt
@@ -0,0 +1,15 @@
+package com.jaino.network.datasource.dictionary
+
+import com.jaino.network.model.response.dictionary.DrinkInfoResponse
+import com.jaino.network.model.response.dictionary.DrinkListResponse
+
+interface DrinkDataSource {
+ suspend fun getDrinkList() : Result>
+
+ suspend fun getDrinkListByType(type: String) : Result
+
+ suspend fun getDrinkByName(name: String) : Result
+
+ suspend fun getDrinkById(id : Long): Result
+
+}
\ No newline at end of file
diff --git a/android/core/network/src/main/java/com/jaino/network/datasource/dictionary/DrinkDataSourceImpl.kt b/android/core/network/src/main/java/com/jaino/network/datasource/dictionary/DrinkDataSourceImpl.kt
new file mode 100644
index 00000000..d4cb82fa
--- /dev/null
+++ b/android/core/network/src/main/java/com/jaino/network/datasource/dictionary/DrinkDataSourceImpl.kt
@@ -0,0 +1,42 @@
+package com.jaino.network.datasource.dictionary
+
+import com.jaino.network.model.response.dictionary.DrinkInfoResponse
+import com.jaino.network.model.response.dictionary.DrinkListResponse
+import com.jaino.network.remote.DictionaryService
+import javax.inject.Inject
+
+class DrinkDataSourceImpl @Inject constructor(
+ private val service : DictionaryService
+): DrinkDataSource{
+ override suspend fun getDrinkList(): Result> =
+ runCatching {
+ service.getDrinkList().data
+ }.onFailure { error ->
+ error.printStackTrace()
+ Result.failure>(error)
+ }
+
+ override suspend fun getDrinkListByType(type: String): Result =
+ runCatching {
+ service.getDrinkListByType(type)
+ }.onFailure { error ->
+ error.printStackTrace()
+ Result.failure>(error)
+ }
+
+ override suspend fun getDrinkByName(name: String): Result =
+ runCatching {
+ service.getDrinkListByName(name)
+ }.onFailure { error ->
+ error.printStackTrace()
+ Result.failure>(error)
+ }
+
+ override suspend fun getDrinkById(id: Long): Result =
+ runCatching {
+ service.getDrinkById(id)
+ }.onFailure {error ->
+ error.printStackTrace()
+ Result.failure(error)
+ }
+}
\ No newline at end of file
diff --git a/android/core/network/src/main/java/com/jaino/network/datasource/rank/RankDataSource.kt b/android/core/network/src/main/java/com/jaino/network/datasource/rank/RankDataSource.kt
new file mode 100644
index 00000000..7a307c68
--- /dev/null
+++ b/android/core/network/src/main/java/com/jaino/network/datasource/rank/RankDataSource.kt
@@ -0,0 +1,10 @@
+package com.jaino.network.datasource.rank
+
+import com.jaino.network.model.response.rank.RankListResponse
+
+interface RankDataSource {
+
+ suspend fun getHighestRatedList(): Result
+
+ suspend fun getMostReviewedList(): Result
+}
\ No newline at end of file
diff --git a/android/core/network/src/main/java/com/jaino/network/datasource/rank/RankDataSourceImpl.kt b/android/core/network/src/main/java/com/jaino/network/datasource/rank/RankDataSourceImpl.kt
new file mode 100644
index 00000000..d7e42ae4
--- /dev/null
+++ b/android/core/network/src/main/java/com/jaino/network/datasource/rank/RankDataSourceImpl.kt
@@ -0,0 +1,25 @@
+package com.jaino.network.datasource.rank
+
+import com.jaino.network.model.response.rank.RankListResponse
+import com.jaino.network.remote.RankService
+import javax.inject.Inject
+
+class RankDataSourceImpl @Inject constructor(
+ private val service: RankService
+): RankDataSource{
+ override suspend fun getHighestRatedList(): Result =
+ runCatching {
+ service.getHighestRatedList()
+ }.onFailure { error ->
+ error.printStackTrace()
+ Result.failure(error)
+ }
+
+ override suspend fun getMostReviewedList(): Result =
+ runCatching {
+ service.getMostReviewedList()
+ }.onFailure { error ->
+ error.printStackTrace()
+ Result.failure(error)
+ }
+}
\ No newline at end of file
diff --git a/android/core/network/src/main/java/com/jaino/network/datasource/review/ReviewDataSource.kt b/android/core/network/src/main/java/com/jaino/network/datasource/review/ReviewDataSource.kt
new file mode 100644
index 00000000..e72bd2da
--- /dev/null
+++ b/android/core/network/src/main/java/com/jaino/network/datasource/review/ReviewDataSource.kt
@@ -0,0 +1,11 @@
+package com.jaino.network.datasource.review
+
+import com.jaino.network.model.request.review.WriteDrinkReviewRequest
+import com.jaino.network.model.response.review.DrinkReviewResponse
+
+interface ReviewDataSource {
+
+ suspend fun getReviewList(drinkId: Long) : Result>
+ suspend fun postReview(drinkId: Long, request : WriteDrinkReviewRequest): Result
+
+}
\ No newline at end of file
diff --git a/android/core/network/src/main/java/com/jaino/network/datasource/review/ReviewDataSourceImpl.kt b/android/core/network/src/main/java/com/jaino/network/datasource/review/ReviewDataSourceImpl.kt
new file mode 100644
index 00000000..3b26b29d
--- /dev/null
+++ b/android/core/network/src/main/java/com/jaino/network/datasource/review/ReviewDataSourceImpl.kt
@@ -0,0 +1,28 @@
+package com.jaino.network.datasource.review
+
+import com.jaino.network.model.request.review.WriteDrinkReviewRequest
+import com.jaino.network.model.response.review.DrinkReviewResponse
+import com.jaino.network.remote.ReviewService
+import javax.inject.Inject
+
+class ReviewDataSourceImpl @Inject constructor(
+ private val service : ReviewService
+): ReviewDataSource{
+ override suspend fun postReview(drinkId: Long, request: WriteDrinkReviewRequest): Result =
+ runCatching {
+ service.postReview(drinkId, request)
+ return Result.success(Unit)
+ }.onFailure { error ->
+ error.printStackTrace()
+ Result.failure(error)
+ }
+
+ override suspend fun getReviewList(drinkId : Long): Result> =
+ runCatching {
+ service.getReviewList(drinkId).reviews
+ }.onFailure { error ->
+ error.printStackTrace()
+ Result.failure>(error)
+ }
+
+}
\ No newline at end of file
diff --git a/android/core/network/src/main/java/com/jaino/network/datasource/setting/ProfileDataSource.kt b/android/core/network/src/main/java/com/jaino/network/datasource/setting/ProfileDataSource.kt
new file mode 100644
index 00000000..5af699f4
--- /dev/null
+++ b/android/core/network/src/main/java/com/jaino/network/datasource/setting/ProfileDataSource.kt
@@ -0,0 +1,12 @@
+package com.jaino.network.datasource.setting
+
+import com.jaino.network.model.request.setting.ProfileRequest
+import com.jaino.network.model.response.auth.MemberResponse
+
+interface ProfileDataSource {
+
+ suspend fun getProfile(): Result
+
+ suspend fun editNickname(profileRequest: ProfileRequest): Result
+
+}
\ No newline at end of file
diff --git a/android/core/network/src/main/java/com/jaino/network/datasource/setting/ProfileDataSourceImpl.kt b/android/core/network/src/main/java/com/jaino/network/datasource/setting/ProfileDataSourceImpl.kt
new file mode 100644
index 00000000..d1b761e7
--- /dev/null
+++ b/android/core/network/src/main/java/com/jaino/network/datasource/setting/ProfileDataSourceImpl.kt
@@ -0,0 +1,27 @@
+package com.jaino.network.datasource.setting
+
+import com.jaino.network.model.request.setting.ProfileRequest
+import com.jaino.network.model.response.auth.MemberResponse
+import com.jaino.network.remote.ProfileService
+import javax.inject.Inject
+
+class ProfileDataSourceImpl @Inject constructor(
+ private val service : ProfileService
+): ProfileDataSource{
+
+ override suspend fun getProfile(): Result =
+ runCatching {
+ service.getProfile()
+ }.onFailure { error ->
+ error.printStackTrace()
+ Result.failure(error)
+ }
+
+ override suspend fun editNickname(profileRequest: ProfileRequest): Result =
+ runCatching {
+ service.editNickname(profileRequest)
+ }.onFailure { error ->
+ error.printStackTrace()
+ Result.failure(error)
+ }
+}
\ No newline at end of file
diff --git a/android/core/network/src/main/java/com/jaino/network/di/ApiModule.kt b/android/core/network/src/main/java/com/jaino/network/di/ApiModule.kt
new file mode 100644
index 00000000..443785c7
--- /dev/null
+++ b/android/core/network/src/main/java/com/jaino/network/di/ApiModule.kt
@@ -0,0 +1,103 @@
+package com.jaino.network.di
+
+import com.jaino.network.BuildConfig
+import com.jaino.network.remote.*
+import com.jaino.network.remote.interceptor.AuthInterceptor
+import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import kotlinx.serialization.json.Json
+import okhttp3.Interceptor
+import okhttp3.MediaType.Companion.toMediaType
+import okhttp3.OkHttpClient
+import okhttp3.logging.HttpLoggingInterceptor
+import retrofit2.Converter
+import retrofit2.Retrofit
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+object ApiModule {
+
+ @Provides
+ @Singleton
+ fun provideJson(): Json = Json {
+ ignoreUnknownKeys = true
+ prettyPrint = true
+ }
+
+ @Provides
+ @Singleton
+ fun provideJsonConverter(json: Json): Converter.Factory =
+ json.asConverterFactory("application/json".toMediaType())
+
+ @Singleton
+ @Provides
+ fun provideAuthInterceptor(authInterceptor: AuthInterceptor): Interceptor = authInterceptor
+
+ @Singleton
+ @Provides
+ fun provideOkHttpInterceptor(
+ authInterceptor: AuthInterceptor
+ ): OkHttpClient {
+ return OkHttpClient.Builder()
+ .addNetworkInterceptor(
+ HttpLoggingInterceptor().apply {
+ level = HttpLoggingInterceptor.Level.BODY
+ }
+ )
+ .addInterceptor(authInterceptor)
+ .build()
+ }
+
+ @Provides
+ @Singleton
+ fun provideRetrofit(
+ factory: Converter.Factory,
+ client: OkHttpClient
+ ): Retrofit {
+ return Retrofit.Builder()
+ .baseUrl(BuildConfig.BASE_DOMAIN_URL)
+ .client(client)
+ .addConverterFactory(factory)
+ .build()
+ }
+
+ @Provides
+ @Singleton
+ fun provideAuthService(
+ retrofit: Retrofit
+ ): AuthService = retrofit.create(AuthService::class.java)
+
+ @Provides
+ @Singleton
+ fun provideDictionaryService(
+ retrofit: Retrofit
+ ): DictionaryService = retrofit.create(DictionaryService::class.java)
+
+ @Provides
+ @Singleton
+ fun provideReviewService(
+ retrofit: Retrofit
+ ): ReviewService = retrofit.create(ReviewService::class.java)
+
+ @Provides
+ @Singleton
+ fun provideAnalysisService(
+ retrofit: Retrofit
+ ): AnalysisService = retrofit.create(AnalysisService::class.java)
+
+ @Provides
+ @Singleton
+ fun provideRankService(
+ retrofit: Retrofit
+ ): RankService = retrofit.create(RankService::class.java)
+
+ @Provides
+ @Singleton
+ fun provideProfileService(
+ retrofit: Retrofit
+ ): ProfileService = retrofit.create(ProfileService::class.java)
+}
diff --git a/android/core/network/src/main/java/com/jaino/network/di/NetworkModule.kt b/android/core/network/src/main/java/com/jaino/network/di/NetworkModule.kt
new file mode 100644
index 00000000..15c76023
--- /dev/null
+++ b/android/core/network/src/main/java/com/jaino/network/di/NetworkModule.kt
@@ -0,0 +1,60 @@
+package com.jaino.network.di
+
+import com.jaino.network.datasource.analysis.AnalysisDataSource
+import com.jaino.network.datasource.analysis.AnalysisDataSourceImpl
+import com.jaino.network.datasource.auth.AuthDataSource
+import com.jaino.network.datasource.auth.AuthDataSourceImpl
+import com.jaino.network.datasource.dictionary.DrinkDataSource
+import com.jaino.network.datasource.dictionary.DrinkDataSourceImpl
+import com.jaino.network.datasource.rank.RankDataSource
+import com.jaino.network.datasource.rank.RankDataSourceImpl
+import com.jaino.network.datasource.review.ReviewDataSource
+import com.jaino.network.datasource.review.ReviewDataSourceImpl
+import com.jaino.network.datasource.setting.ProfileDataSource
+import com.jaino.network.datasource.setting.ProfileDataSourceImpl
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+abstract class NetworkModule {
+
+ @Binds
+ @Singleton
+ abstract fun provideAuthDataSource(
+ authDataSourceImpl : AuthDataSourceImpl
+ ): AuthDataSource
+
+ @Binds
+ @Singleton
+ abstract fun provideDrinkDataSource(
+ drinkDataSourceImpl: DrinkDataSourceImpl
+ ): DrinkDataSource
+
+ @Binds
+ @Singleton
+ abstract fun provideReviewDataSource(
+ reviewDataSourceImpl: ReviewDataSourceImpl
+ ): ReviewDataSource
+
+ @Binds
+ @Singleton
+ abstract fun provideAnalysisSourceDataSource(
+ analysisDataSourceImpl : AnalysisDataSourceImpl
+ ): AnalysisDataSource
+
+ @Binds
+ @Singleton
+ abstract fun provideRankDataSource(
+ rankDataSourceImpl : RankDataSourceImpl
+ ): RankDataSource
+
+ @Binds
+ @Singleton
+ abstract fun provideProfileDataSource(
+ profileDataSourceImpl : ProfileDataSourceImpl
+ ): ProfileDataSource
+}
\ No newline at end of file
diff --git a/android/core/network/src/main/java/com/jaino/network/model/request/analysis/AnalysisSourceRequest.kt b/android/core/network/src/main/java/com/jaino/network/model/request/analysis/AnalysisSourceRequest.kt
new file mode 100644
index 00000000..c8ce39b0
--- /dev/null
+++ b/android/core/network/src/main/java/com/jaino/network/model/request/analysis/AnalysisSourceRequest.kt
@@ -0,0 +1,10 @@
+package com.jaino.network.model.request.analysis
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class AnalysisSourceRequest(
+ val date : String,
+ val textExpression : String,
+ val facialExpression: String
+)
\ No newline at end of file
diff --git a/android/core/network/src/main/java/com/jaino/network/model/request/review/WriteDrinkReviewRequest.kt b/android/core/network/src/main/java/com/jaino/network/model/request/review/WriteDrinkReviewRequest.kt
new file mode 100644
index 00000000..61433bb2
--- /dev/null
+++ b/android/core/network/src/main/java/com/jaino/network/model/request/review/WriteDrinkReviewRequest.kt
@@ -0,0 +1,11 @@
+package com.jaino.network.model.request.review
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class WriteDrinkReviewRequest (
+ val userId : Long,
+ val comment : String,
+ val score : Int,
+ val date : String
+)
diff --git a/android/core/network/src/main/java/com/jaino/network/model/request/setting/ProfileRequest.kt b/android/core/network/src/main/java/com/jaino/network/model/request/setting/ProfileRequest.kt
new file mode 100644
index 00000000..6fc443d5
--- /dev/null
+++ b/android/core/network/src/main/java/com/jaino/network/model/request/setting/ProfileRequest.kt
@@ -0,0 +1,9 @@
+package com.jaino.network.model.request.setting
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class ProfileRequest(
+ val userId : Long,
+ val newNickname : String
+)
\ No newline at end of file
diff --git a/android/core/network/src/main/java/com/jaino/network/model/response/analysis/AnalysisHistoryResponse.kt b/android/core/network/src/main/java/com/jaino/network/model/response/analysis/AnalysisHistoryResponse.kt
new file mode 100644
index 00000000..daea4ba5
--- /dev/null
+++ b/android/core/network/src/main/java/com/jaino/network/model/response/analysis/AnalysisHistoryResponse.kt
@@ -0,0 +1,17 @@
+package com.jaino.network.model.response.analysis
+
+import com.jaino.model.analysis.AnalysisHistory
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class AnalysisHistoryResponse (
+ val id: Long,
+ val date: String,
+ val sentiment : String
+){
+ fun toAnalyzeHistory() : AnalysisHistory = AnalysisHistory(
+ id = id,
+ date = date.replace("T", " ").slice(0 until date.length-3),
+ sentiment = sentiment
+ )
+}
\ No newline at end of file
diff --git a/android/core/network/src/main/java/com/jaino/network/model/response/analysis/AnalysisIdResponse.kt b/android/core/network/src/main/java/com/jaino/network/model/response/analysis/AnalysisIdResponse.kt
new file mode 100644
index 00000000..ca1f6409
--- /dev/null
+++ b/android/core/network/src/main/java/com/jaino/network/model/response/analysis/AnalysisIdResponse.kt
@@ -0,0 +1,12 @@
+package com.jaino.network.model.response.analysis
+
+import com.jaino.model.analysis.AnalysisId
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class AnalysisIdResponse(
+ @SerialName("id") val analysisId : Long
+){
+ fun toAnalysisId(): AnalysisId = AnalysisId(analysisId)
+}
\ No newline at end of file
diff --git a/android/core/network/src/main/java/com/jaino/network/model/response/analysis/SentimentAnalysisResponse.kt b/android/core/network/src/main/java/com/jaino/network/model/response/analysis/SentimentAnalysisResponse.kt
new file mode 100644
index 00000000..0a7fcee0
--- /dev/null
+++ b/android/core/network/src/main/java/com/jaino/network/model/response/analysis/SentimentAnalysisResponse.kt
@@ -0,0 +1,39 @@
+package com.jaino.network.model.response.analysis
+
+import com.jaino.model.analysis.SentimentAnalysis
+import kotlinx.serialization.Serializable
+
+
+@Serializable
+data class SentimentAnalysisResponse(
+ val id: Long,
+ val textExpression: String,
+ val facialExpression: String,
+ val sentiment: String,
+ val date : String,
+ val drinkId : Long,
+ val name : String,
+ val dosu : Double,
+ val price : Int,
+ val volume : Int,
+ val drinkImage: String,
+ val type : String,
+ val sweetness : Int
+){
+ fun toSentimentAnalysis() : SentimentAnalysis = SentimentAnalysis(
+ id = id,
+ textExpression = textExpression,
+ facialExpression = facialExpression,
+ sentiment = sentiment.filter { it.isLetter() },
+ level = sentiment.filter { it.isDigit() }.toFloat(),
+ date = date,
+ drinkId = drinkId,
+ name = name,
+ dosu = dosu,
+ price = price,
+ volume = volume,
+ drinkImage = drinkImage,
+ type = type,
+ sweetness = sweetness
+ )
+}
\ No newline at end of file
diff --git a/android/core/network/src/main/java/com/jaino/network/model/response/auth/AuthTokenResponse.kt b/android/core/network/src/main/java/com/jaino/network/model/response/auth/AuthTokenResponse.kt
new file mode 100644
index 00000000..672451de
--- /dev/null
+++ b/android/core/network/src/main/java/com/jaino/network/model/response/auth/AuthTokenResponse.kt
@@ -0,0 +1,9 @@
+package com.jaino.network.model.response.auth
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class AuthTokenResponse (
+ val access: String,
+ val refresh: String
+)
\ No newline at end of file
diff --git a/android/core/network/src/main/java/com/jaino/network/model/response/auth/SignInResponse.kt b/android/core/network/src/main/java/com/jaino/network/model/response/auth/SignInResponse.kt
new file mode 100644
index 00000000..d87f35a5
--- /dev/null
+++ b/android/core/network/src/main/java/com/jaino/network/model/response/auth/SignInResponse.kt
@@ -0,0 +1,35 @@
+package com.jaino.network.model.response.auth
+
+import com.jaino.model.auth.User
+import com.jaino.model.setting.Profile
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class SignInResponse (
+ val memberResponse : MemberResponse,
+ val token : TokenResponse
+){
+ fun toSignIn(): User = User(
+ refreshToken = token.refresh,
+ accessToken = token.access,
+ userId = memberResponse.id,
+ nickName = memberResponse.nickname
+ )
+}
+
+@Serializable
+data class MemberResponse(
+ val id : Long,
+ val nickname : String
+){
+ fun toProfile(): Profile = Profile(
+ userId = id,
+ nickname = nickname
+ )
+}
+
+@Serializable
+data class TokenResponse(
+ val access: String,
+ val refresh: String
+)
\ No newline at end of file
diff --git a/android/core/network/src/main/java/com/jaino/network/model/response/base/BaseResponse.kt b/android/core/network/src/main/java/com/jaino/network/model/response/base/BaseResponse.kt
new file mode 100644
index 00000000..b533135d
--- /dev/null
+++ b/android/core/network/src/main/java/com/jaino/network/model/response/base/BaseResponse.kt
@@ -0,0 +1,11 @@
+package com.jaino.network.model.response.base
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class BaseResponse(
+ val status: Int,
+ val success: Boolean,
+ val message: String,
+ val data: T
+)
\ No newline at end of file
diff --git a/android/core/network/src/main/java/com/jaino/network/model/response/base/EmptyResponse.kt b/android/core/network/src/main/java/com/jaino/network/model/response/base/EmptyResponse.kt
new file mode 100644
index 00000000..35d223b3
--- /dev/null
+++ b/android/core/network/src/main/java/com/jaino/network/model/response/base/EmptyResponse.kt
@@ -0,0 +1,10 @@
+package com.jaino.network.model.response.base
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class EmptyResponse(
+ val status: Int,
+ val success: Boolean,
+ val message: String,
+)
\ No newline at end of file
diff --git a/android/core/network/src/main/java/com/jaino/network/model/response/dictionary/DrinkInfoResponse.kt b/android/core/network/src/main/java/com/jaino/network/model/response/dictionary/DrinkInfoResponse.kt
new file mode 100644
index 00000000..a3b37ea7
--- /dev/null
+++ b/android/core/network/src/main/java/com/jaino/network/model/response/dictionary/DrinkInfoResponse.kt
@@ -0,0 +1,36 @@
+package com.jaino.network.model.response.dictionary
+
+import com.jaino.model.dictionary.Drink
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class DrinkListResponse(
+ val drinks : List
+)
+
+@Serializable
+data class DrinkInfoResponse(
+ val id : Long,
+ val name : String,
+ val dosu : Double,
+ val sweetness : Int,
+ val price : Int,
+ val volume : Int,
+ val image : String,
+ val type : String,
+ val rating : Double,
+ val reviewCount: Int
+){
+ fun toDrinkInfo(): Drink = Drink(
+ id = id,
+ name = name,
+ dosu = dosu,
+ sweetness = sweetness,
+ price = price,
+ volume = volume,
+ image = image,
+ type = type,
+ rating = rating.toFloat(),
+ reviewCount = reviewCount
+ )
+}
\ No newline at end of file
diff --git a/android/core/network/src/main/java/com/jaino/network/model/response/rank/RankResponse.kt b/android/core/network/src/main/java/com/jaino/network/model/response/rank/RankResponse.kt
new file mode 100644
index 00000000..e4067019
--- /dev/null
+++ b/android/core/network/src/main/java/com/jaino/network/model/response/rank/RankResponse.kt
@@ -0,0 +1,28 @@
+package com.jaino.network.model.response.rank
+
+import com.jaino.model.rank.Rank
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class RankListResponse(
+ val ranking : List
+)
+
+@Serializable
+data class RankResponse(
+ val id : Long,
+ val name : String,
+ val type : String,
+ val rating : Double,
+ val reviewCount : Int,
+ val image : String,
+){
+ fun toRank() : Rank = Rank(
+ id = id,
+ name = name,
+ type = type,
+ rating = rating,
+ reviewCount = reviewCount,
+ image = image
+ )
+}
\ No newline at end of file
diff --git a/android/core/network/src/main/java/com/jaino/network/model/response/review/DrinkReviewResponse.kt b/android/core/network/src/main/java/com/jaino/network/model/response/review/DrinkReviewResponse.kt
new file mode 100644
index 00000000..dbda5ed2
--- /dev/null
+++ b/android/core/network/src/main/java/com/jaino/network/model/response/review/DrinkReviewResponse.kt
@@ -0,0 +1,28 @@
+package com.jaino.network.model.response.review
+
+import com.jaino.model.review.Review
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+
+@Serializable
+data class DrinkReviewListResponse(
+ val reviews : List
+)
+@Serializable
+data class DrinkReviewResponse (
+ @SerialName("id") val reviewId : Long,
+ val score : Int,
+ val date : String,
+ val comment : String,
+ val nickname : String,
+ @SerialName("drinkId") val drinkId : Long
+ ){
+ fun toDrinkReview(): Review = Review(
+ reviewId = reviewId,
+ comment = comment,
+ score = score,
+ date = date,
+ nickname = nickname,
+ )
+}
diff --git a/android/core/network/src/main/java/com/jaino/network/remote/AnalysisService.kt b/android/core/network/src/main/java/com/jaino/network/remote/AnalysisService.kt
new file mode 100644
index 00000000..74d865e5
--- /dev/null
+++ b/android/core/network/src/main/java/com/jaino/network/remote/AnalysisService.kt
@@ -0,0 +1,24 @@
+package com.jaino.network.remote
+
+import com.jaino.network.model.request.analysis.AnalysisSourceRequest
+import com.jaino.network.model.response.analysis.AnalysisIdResponse
+import com.jaino.network.model.response.base.BaseResponse
+import com.jaino.network.model.response.analysis.AnalysisHistoryResponse
+import com.jaino.network.model.response.analysis.SentimentAnalysisResponse
+import retrofit2.http.*
+
+interface AnalysisService {
+
+ @POST("/analyze/sources")
+ suspend fun postAnalysisSource(
+ @Body analysisSourceRequest: AnalysisSourceRequest
+ ) : AnalysisIdResponse
+
+ @GET("/analyze")
+ suspend fun getAnalyzeList() : List
+
+ @GET("/analyze/{analysis-id}")
+ suspend fun getSentimentAnalysis(
+ @Path("analysis-id") analysisId : Long
+ ) : SentimentAnalysisResponse
+}
\ No newline at end of file
diff --git a/android/core/network/src/main/java/com/jaino/network/remote/AuthService.kt b/android/core/network/src/main/java/com/jaino/network/remote/AuthService.kt
new file mode 100644
index 00000000..ea2c79c7
--- /dev/null
+++ b/android/core/network/src/main/java/com/jaino/network/remote/AuthService.kt
@@ -0,0 +1,14 @@
+package com.jaino.network.remote
+
+import com.jaino.network.model.response.auth.SignInResponse
+
+import retrofit2.http.GET
+import retrofit2.http.Query
+
+interface AuthService {
+
+ @GET("/auth/login")
+ suspend fun signIn(
+ @Query("token") token: String
+ ) : SignInResponse
+}
\ No newline at end of file
diff --git a/android/core/network/src/main/java/com/jaino/network/remote/DictionaryService.kt b/android/core/network/src/main/java/com/jaino/network/remote/DictionaryService.kt
new file mode 100644
index 00000000..80b93fa5
--- /dev/null
+++ b/android/core/network/src/main/java/com/jaino/network/remote/DictionaryService.kt
@@ -0,0 +1,32 @@
+package com.jaino.network.remote
+
+import com.jaino.network.model.response.base.BaseResponse
+import com.jaino.network.model.response.dictionary.DrinkInfoResponse
+import com.jaino.network.model.response.dictionary.DrinkListResponse
+import retrofit2.http.GET
+import retrofit2.http.Path
+
+interface DictionaryService {
+
+ // ์ ์ฒด ์ฃผ๋ฅ ๋ฆฌ์คํธ ์กฐํ
+ @GET("/drinks/all")
+ suspend fun getDrinkList(): BaseResponse>
+
+ // ์ด๋ฆ์ผ๋ก ์ฃผ๋ฅ ์กฐํ
+ @GET("/drinks/name/{name}")
+ suspend fun getDrinkListByName(
+ @Path("name") name: String
+ ): DrinkListResponse
+
+ // type ์ผ๋ก ์ฃผ๋ฅ ์กฐํ
+ @GET("/drinks/type/{type}")
+ suspend fun getDrinkListByType(
+ @Path("type") type: String
+ ): DrinkListResponse
+
+ // id ๊ฐ์ผ๋ก ์ฃผ๋ฅ ์กฐํ
+ @GET("/drinks/{drink-id}")
+ suspend fun getDrinkById(
+ @Path("drink-id") id: Long
+ ): DrinkInfoResponse
+}
diff --git a/android/core/network/src/main/java/com/jaino/network/remote/ProfileService.kt b/android/core/network/src/main/java/com/jaino/network/remote/ProfileService.kt
new file mode 100644
index 00000000..4c6861b6
--- /dev/null
+++ b/android/core/network/src/main/java/com/jaino/network/remote/ProfileService.kt
@@ -0,0 +1,19 @@
+package com.jaino.network.remote
+
+import com.jaino.network.model.request.setting.ProfileRequest
+import com.jaino.network.model.response.auth.MemberResponse
+import retrofit2.http.Body
+import retrofit2.http.GET
+import retrofit2.http.PUT
+import retrofit2.http.Path
+
+interface ProfileService {
+
+ @GET("/member")
+ suspend fun getProfile(): MemberResponse
+
+ @PUT("/member/nickname")
+ suspend fun editNickname(
+ @Body profileRequest : ProfileRequest
+ ): MemberResponse
+}
\ No newline at end of file
diff --git a/android/core/network/src/main/java/com/jaino/network/remote/RankService.kt b/android/core/network/src/main/java/com/jaino/network/remote/RankService.kt
new file mode 100644
index 00000000..c0e1c436
--- /dev/null
+++ b/android/core/network/src/main/java/com/jaino/network/remote/RankService.kt
@@ -0,0 +1,12 @@
+package com.jaino.network.remote
+
+import com.jaino.network.model.response.rank.RankListResponse
+import retrofit2.http.GET
+
+interface RankService {
+ @GET("/drinks/rankings/rating")
+ suspend fun getHighestRatedList(): RankListResponse
+
+ @GET("/drinks/rankings/review")
+ suspend fun getMostReviewedList(): RankListResponse
+}
\ No newline at end of file
diff --git a/android/core/network/src/main/java/com/jaino/network/remote/ReviewService.kt b/android/core/network/src/main/java/com/jaino/network/remote/ReviewService.kt
new file mode 100644
index 00000000..32df8361
--- /dev/null
+++ b/android/core/network/src/main/java/com/jaino/network/remote/ReviewService.kt
@@ -0,0 +1,22 @@
+package com.jaino.network.remote
+
+import com.jaino.network.model.response.base.EmptyResponse
+import com.jaino.network.model.request.review.WriteDrinkReviewRequest
+import com.jaino.network.model.response.review.DrinkReviewListResponse
+import retrofit2.http.*
+
+
+interface ReviewService {
+
+ @GET("/drinks/{drink-id}/reviews")
+ suspend fun getReviewList(
+ @Path("drink-id") drink_id : Long
+ ): DrinkReviewListResponse
+
+ @POST("/drinks/{drink-id}/reviews")
+ suspend fun postReview(
+ @Path("drink-id") drink_id : Long,
+ @Body writeDrinkReviewRequest : WriteDrinkReviewRequest
+ )
+
+}
diff --git a/android/core/network/src/main/java/com/jaino/network/remote/interceptor/AuthInterceptor.kt b/android/core/network/src/main/java/com/jaino/network/remote/interceptor/AuthInterceptor.kt
new file mode 100644
index 00000000..d46e3e98
--- /dev/null
+++ b/android/core/network/src/main/java/com/jaino/network/remote/interceptor/AuthInterceptor.kt
@@ -0,0 +1,66 @@
+package com.jaino.network.remote.interceptor
+
+import android.content.Context
+import com.jaino.datastore.BeJuRyuDatastore
+import com.jaino.network.BuildConfig
+import com.jaino.network.model.response.auth.AuthTokenResponse
+import com.jakewharton.processphoenix.ProcessPhoenix
+import dagger.hilt.android.qualifiers.ApplicationContext
+import kotlinx.serialization.decodeFromString
+import kotlinx.serialization.json.Json
+import okhttp3.Interceptor
+import okhttp3.Response
+import timber.log.Timber
+import javax.inject.Inject
+
+class AuthInterceptor @Inject constructor(
+ private val dataStore: BeJuRyuDatastore,
+ private val json: Json,
+ @ApplicationContext private val context: Context
+): Interceptor {
+ override fun intercept(chain: Interceptor.Chain): Response {
+ val originalRequest = chain.request()
+ val authRequest = originalRequest.newBuilder()
+ .addHeader("Authorization", "Bearer ${dataStore.accessToken}")
+ .build()
+ val response = chain.proceed(authRequest)
+ when (response.code) {
+ // ์ก์ธ์ค ํ ํฐ ๋ง๋ฃ
+ 401 -> {
+ try {
+ val refreshTokenRequest = originalRequest.newBuilder().get()
+ .url("${BuildConfig.BASE_DOMAIN_URL}/auth/refresh")
+ .addHeader("accessToken", dataStore.accessToken)
+ .addHeader("refreshToken", dataStore.refreshToken)
+ .build()
+ val refreshTokenResponse = chain.proceed(refreshTokenRequest)
+
+ if (refreshTokenResponse.isSuccessful) {
+ val responseToken: AuthTokenResponse =
+ json.decodeFromString(
+ requireNotNull(refreshTokenResponse.body?.string())
+ )
+ with(dataStore) {
+ accessToken = responseToken.access
+ refreshToken = responseToken.refresh
+ }
+ refreshTokenResponse.close()
+ val newRequest = originalRequest.newBuilder()
+ .addHeader("Authorization", "Bearer ${dataStore.accessToken}")
+ .build()
+ return chain.proceed(newRequest)
+ } else {
+ dataStore.accessToken = ""
+ dataStore.refreshToken = ""
+ }
+ } catch (e: Throwable) {
+ Timber.e(e)
+ dataStore.accessToken = ""
+ dataStore.refreshToken = ""
+ ProcessPhoenix.triggerRebirth(context)
+ }
+ }
+ }
+ return response
+ }
+}
\ No newline at end of file
diff --git a/android/core/network/src/test/java/com/jaino/network/ExampleUnitTest.kt b/android/core/network/src/test/java/com/jaino/network/ExampleUnitTest.kt
new file mode 100644
index 00000000..a0181758
--- /dev/null
+++ b/android/core/network/src/test/java/com/jaino/network/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.jaino.network
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * 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)
+ }
+}
\ No newline at end of file
diff --git a/android/feature/account/.gitignore b/android/feature/account/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/android/feature/account/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/android/feature/account/build.gradle.kts b/android/feature/account/build.gradle.kts
new file mode 100644
index 00000000..9ae66989
--- /dev/null
+++ b/android/feature/account/build.gradle.kts
@@ -0,0 +1,29 @@
+plugins {
+ id("com.jaino.feature")
+ id("com.jaino.hilt")
+}
+
+android {
+ namespace = "com.jaino.account"
+
+ defaultConfig {
+ consumerProguardFiles("consumer-rules.pro")
+ }
+}
+
+dependencies {
+ implementation(project(":core:data"))
+ implementation(project(":core:domain"))
+ implementation(project(":core:model"))
+ implementation(project(":core:common"))
+ implementation(project(":core:designsystem"))
+ implementation(libs.timber)
+ implementation(libs.androidx.fragment)
+ implementation(libs.bundles.androidx)
+ implementation(libs.androidx.lifecycle.runtime)
+ implementation(libs.material)
+ implementation(libs.processphoenix)
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.androidx.test.junit)
+ androidTestImplementation(libs.androidx.test.espresso)
+}
\ No newline at end of file
diff --git a/android/feature/account/consumer-rules.pro b/android/feature/account/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/android/feature/account/proguard-rules.pro b/android/feature/account/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/android/feature/account/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/android/feature/account/src/androidTest/java/com/jaino/account/ExampleInstrumentedTest.kt b/android/feature/account/src/androidTest/java/com/jaino/account/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..834a6a82
--- /dev/null
+++ b/android/feature/account/src/androidTest/java/com/jaino/account/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.jaino.account
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.jaino.account.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/android/feature/account/src/main/AndroidManifest.xml b/android/feature/account/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a5918e68
--- /dev/null
+++ b/android/feature/account/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/android/feature/account/src/main/java/com/jaino/account/AccountActivity.kt b/android/feature/account/src/main/java/com/jaino/account/AccountActivity.kt
new file mode 100644
index 00000000..684153d7
--- /dev/null
+++ b/android/feature/account/src/main/java/com/jaino/account/AccountActivity.kt
@@ -0,0 +1,78 @@
+package com.jaino.account
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import androidx.databinding.DataBindingUtil
+import com.jaino.account.databinding.ActivityAccountBinding
+import com.jaino.common.navigation.AppNavigator
+import com.jaino.common.widget.ConfirmDialog
+import com.jaino.data.repository.auth.SocialAuthRepository
+import com.jaino.data.repository.user.LocalUserRepository
+import com.jakewharton.processphoenix.ProcessPhoenix
+import dagger.hilt.android.AndroidEntryPoint
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class AccountActivity : AppCompatActivity() {
+
+ @Inject
+ lateinit var socialAuth : SocialAuthRepository
+
+ @Inject
+ lateinit var localData : LocalUserRepository
+
+ @Inject
+ lateinit var appNavigator : AppNavigator
+
+ private var _binding: ActivityAccountBinding? = null
+ private val binding
+ get() = requireNotNull(_binding) { "binding object is not initialized" }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ _binding = DataBindingUtil.setContentView(this, R.layout.activity_account)
+ binding.lifecycleOwner = this
+ initButtons()
+ }
+
+ private fun initButtons(){
+ binding.accountBackButton.setOnClickListener {
+ navigateToSetting()
+ }
+
+ binding.deleteAccountCardView.setOnClickListener {
+ ConfirmDialog(
+ this,
+ "๊ณ์ ์ ์ญ์ ํ์๊ฒ ์ต๋๊น?",
+ onDoneButtonClick = {
+ socialAuth.unlink()
+ localData.clear()
+ ProcessPhoenix.triggerRebirth(this)
+ }
+ ).show()
+ }
+
+ binding.signOutCardView.setOnClickListener {
+ ConfirmDialog(
+ this,
+ "๋ก๊ทธ์์์ ํ์๊ฒ ์ต๋๊น?",
+ onDoneButtonClick = {
+ socialAuth.signOut()
+ localData.clear()
+ ProcessPhoenix.triggerRebirth(this)
+ }
+ ).show()
+ }
+ }
+
+ private fun navigateToSetting(){
+ startActivity(appNavigator.navigateToHome("BeJuRyu://feature/setting"))
+ }
+
+ companion object {
+ fun getIntent(context: Context): Intent =
+ Intent(context, AccountActivity::class.java)
+ }
+}
\ No newline at end of file
diff --git a/android/feature/account/src/main/res/layout/activity_account.xml b/android/feature/account/src/main/res/layout/activity_account.xml
new file mode 100644
index 00000000..21056847
--- /dev/null
+++ b/android/feature/account/src/main/res/layout/activity_account.xml
@@ -0,0 +1,136 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/feature/account/src/test/java/com/jaino/account/ExampleUnitTest.kt b/android/feature/account/src/test/java/com/jaino/account/ExampleUnitTest.kt
new file mode 100644
index 00000000..d896e323
--- /dev/null
+++ b/android/feature/account/src/test/java/com/jaino/account/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.jaino.account
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * 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)
+ }
+}
\ No newline at end of file
diff --git a/android/feature/analysis/.gitignore b/android/feature/analysis/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/android/feature/analysis/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/android/feature/analysis/build.gradle.kts b/android/feature/analysis/build.gradle.kts
new file mode 100644
index 00000000..6ff651e5
--- /dev/null
+++ b/android/feature/analysis/build.gradle.kts
@@ -0,0 +1,34 @@
+plugins {
+ id("com.jaino.feature")
+ id("com.jaino.hilt")
+ alias(libs.plugins.androidx.navigation.safeargs)
+}
+
+android {
+ namespace = "com.jaino.analysis"
+
+ defaultConfig {
+ consumerProguardFiles("consumer-rules.pro")
+ }
+}
+
+dependencies {
+ implementation(project(":core:data"))
+ implementation(project(":core:domain"))
+ implementation(project(":core:model"))
+ implementation(project(":core:common"))
+ implementation(project(":core:designsystem"))
+ implementation(libs.bundles.camerax)
+ implementation(libs.glide)
+ implementation(libs.timber)
+ implementation(libs.kotlin.datetime)
+ implementation(libs.androidx.fragment)
+ implementation(libs.bundles.navigation)
+ implementation(libs.material)
+ implementation(libs.progressView)
+ implementation(libs.lottie)
+ implementation(libs.kakao.share)
+ implementation(libs.bundles.androidx)
+ implementation(libs.androidx.lifecycle.runtime)
+ implementation(libs.androidx.lifecycle.livedata)
+}
\ No newline at end of file
diff --git a/android/feature/analysis/consumer-rules.pro b/android/feature/analysis/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/android/feature/analysis/proguard-rules.pro b/android/feature/analysis/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/android/feature/analysis/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/android/feature/analysis/src/androidTest/java/com/jaino/analysis/ExampleInstrumentedTest.kt b/android/feature/analysis/src/androidTest/java/com/jaino/analysis/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..bd4cf365
--- /dev/null
+++ b/android/feature/analysis/src/androidTest/java/com/jaino/analysis/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.jaino.analysis
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.jaino.analyze.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/android/feature/analysis/src/main/AndroidManifest.xml b/android/feature/analysis/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a5918e68
--- /dev/null
+++ b/android/feature/analysis/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/android/feature/analysis/src/main/java/com/jaino/analysis/camera/CameraFragment.kt b/android/feature/analysis/src/main/java/com/jaino/analysis/camera/CameraFragment.kt
new file mode 100644
index 00000000..d682278e
--- /dev/null
+++ b/android/feature/analysis/src/main/java/com/jaino/analysis/camera/CameraFragment.kt
@@ -0,0 +1,184 @@
+package com.jaino.analysis.camera
+
+import android.content.ContentValues
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.media.MediaActionSound
+import android.os.Bundle
+import android.provider.MediaStore
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.camera.core.*
+import androidx.camera.lifecycle.ProcessCameraProvider
+import androidx.camera.view.PreviewView
+import androidx.core.content.ContextCompat
+import androidx.databinding.DataBindingUtil
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import androidx.navigation.fragment.findNavController
+import com.google.common.util.concurrent.ListenableFuture
+import com.jaino.analysis.R
+import com.jaino.analysis.databinding.FragmentCameraBinding
+import com.jaino.common.extensions.getCurrentFileName
+import kotlinx.coroutines.launch
+import timber.log.Timber
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.Executors
+
+class CameraFragment : Fragment() {
+
+ private var _binding: FragmentCameraBinding? = null
+ private val binding
+ get() = requireNotNull(_binding) { "binding object is not initialized" }
+
+ private var lensFacing: Int = CameraSelector.LENS_FACING_BACK // ํ๋ฉด ๋น์จ
+ private var screenAspectRatio = AspectRatio.RATIO_4_3 // 3 : 4๋น์จ
+
+ private lateinit var cameraProviderFuture : ListenableFuture
+ private lateinit var imageCapture : ImageCapture
+ private lateinit var imagePreview : Preview
+ private lateinit var imageAnalyzer : ImageAnalysis
+
+ private lateinit var cameraExecutor: ExecutorService
+ private lateinit var cameraSound: MediaActionSound
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ _binding = DataBindingUtil.inflate(inflater, R.layout.fragment_camera, container, false)
+ binding.lifecycleOwner = viewLifecycleOwner
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ initCamera()
+ setUpCamera()
+ initButton()
+ }
+
+ private fun initCamera(){
+ cameraExecutor = Executors.newSingleThreadExecutor() // init executors
+ cameraSound = MediaActionSound() // init cameraSound
+ }
+
+ private fun setUpCamera(){
+ cameraProviderFuture = ProcessCameraProvider.getInstance(requireActivity())
+ cameraProviderFuture.addListener(
+ {
+ bindCameraUseCases()
+ },
+ ContextCompat.getMainExecutor(requireContext())
+ )
+ }
+
+ private fun initButton(){
+ binding.SwitchCameraButton.setOnClickListener{
+ lensFacing = if (CameraSelector.LENS_FACING_FRONT == lensFacing) {
+ CameraSelector.LENS_FACING_BACK
+ } else {
+ CameraSelector.LENS_FACING_FRONT
+ }
+ // ์นด๋ฉ๋ผ ๋ ์ฆ ๋ฐ์ธ๋ฉ ๋ณ๊ฒฝ์ผ๋ก, UseCases ์ฌ์ค์
+ bindCameraUseCases()
+ }
+ binding.CaptureImageButton.setOnClickListener{
+ captureImage()
+ }
+ }
+
+
+ private fun bindCameraUseCases(){
+ val cameraProvider = cameraProviderFuture.get()
+ val cameraSelector : CameraSelector = CameraSelector.Builder() // ์นด๋ฉ๋ผ ์ต์
+ .requireLensFacing(lensFacing) // ํ๋ฉด ์นด๋ฉ๋ผ
+ .build()
+
+ imagePreview = Preview.Builder() // preview ๊ฐ์ฒด ์์ฑ
+ .setTargetAspectRatio(screenAspectRatio) // ๋น์จ ์ค์
+ .build()
+
+ imageAnalyzer = ImageAnalysis.Builder()
+ .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
+ .build()
+
+ imageCapture = ImageCapture.Builder()
+ .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) // ํจ์จ ์ต๋์น
+ .setTargetAspectRatio(screenAspectRatio)
+ .build()
+
+ cameraProvider.unbindAll() // for rebinding
+
+ imagePreview.setSurfaceProvider(binding.CameraPreview.surfaceProvider) // view ์ ๊ฐ์ฒด ๊ฒฐํฉ
+ binding.CameraPreview.scaleType = PreviewView.ScaleType.FIT_CENTER
+
+ cameraProvider.bindToLifecycle(this, cameraSelector, imagePreview,
+ imageAnalyzer, imageCapture) // ๋ผ์ดํ ์ฌ์ดํด ๋ฐ์ธ๋ฉ
+ }
+
+ private fun captureImage(){
+ val outputFileOptions = ImageCapture.OutputFileOptions.Builder(
+ requireContext().contentResolver,
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI, ContentValues().getCurrentFileName()
+ ).build()
+
+ imageCapture.takePicture(outputFileOptions, cameraExecutor,
+ object : ImageCapture.OnImageSavedCallback {
+ override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
+ navigateToImage(outputFileResults.savedUri.toString())
+ }
+
+ override fun onError(exception: ImageCaptureException) {
+ Timber.e(exception)
+ }
+ }
+ )
+
+ // Capture effect
+ startCameraSound()
+ startCameraScreenAnimation()
+ }
+
+ private fun startCameraSound(){
+ cameraSound.play(MediaActionSound.SHUTTER_CLICK) // camera sound
+ }
+
+ private fun startCameraScreenAnimation(){
+ // Display flash animation to indicate that photo was captured
+ binding.root.postDelayed({
+ binding.root.foreground = ColorDrawable(Color.WHITE)
+ binding.root.postDelayed(
+ { binding.root.foreground = null }, ANIMATION_FAST_MILLIS
+ )
+ }, ANIMATION_SLOW_MILLIS)
+ }
+
+ private fun navigateToImage(uri : String){
+ lifecycleScope.launch{
+ lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED){
+ findNavController().navigate(
+ CameraFragmentDirections.actionCameraFragmentToImageInputFragment(
+ uri, ""
+ )
+ )
+ }
+ }
+ }
+
+ override fun onDestroy() {
+ _binding = null
+ super.onDestroy()
+ cameraExecutor.shutdown()
+ cameraSound.release()
+ }
+
+ companion object {
+ const val ANIMATION_FAST_MILLIS = 100L
+ const val ANIMATION_SLOW_MILLIS = 200L
+ }
+}
\ No newline at end of file
diff --git a/android/feature/analysis/src/main/java/com/jaino/analysis/camera/PermissionsFragment.kt b/android/feature/analysis/src/main/java/com/jaino/analysis/camera/PermissionsFragment.kt
new file mode 100644
index 00000000..39ef0910
--- /dev/null
+++ b/android/feature/analysis/src/main/java/com/jaino/analysis/camera/PermissionsFragment.kt
@@ -0,0 +1,62 @@
+package com.jaino.analysis.camera
+
+import android.Manifest
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.Bundle
+import android.widget.Toast
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.core.content.ContextCompat
+import androidx.fragment.app.Fragment
+import androidx.navigation.fragment.findNavController
+
+private var PERMISSIONS_REQUIRED = arrayOf(Manifest.permission.CAMERA)
+
+class PermissionsFragment : Fragment() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ // ๋ฒ์ 9 ์ดํ์ธ ๊ฒฝ์ฐ ์ธ๋ถ ์ ์ฅ์ ์ ๊ทผ ๊ถํ ์ถ๊ฐ
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
+ val permissionList = PERMISSIONS_REQUIRED.toMutableList()
+ permissionList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ PERMISSIONS_REQUIRED = permissionList.toTypedArray()
+ }
+
+ if (!hasPermissions(requireContext())) {
+ // ์นด๋ฉ๋ผ ๊ถํ ์์ฒญ
+ activityResultLauncher.launch(PERMISSIONS_REQUIRED)
+ } else {
+ navigateToCamera()
+ }
+ }
+
+ private fun navigateToCamera() {
+ val direction = PermissionsFragmentDirections.actionPermissionsFragmentToCameraFragment()
+ findNavController().navigate(direction)
+ }
+
+ private val activityResultLauncher =
+ registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions())
+ { permissions ->
+ // Handle Permission granted/rejected
+ var permissionGranted = true
+ permissions.entries.forEach {
+ if (it.key in PERMISSIONS_REQUIRED && it.value == false)
+ permissionGranted = false
+ }
+ if (!permissionGranted) {
+ Toast.makeText(context, "Permission request denied", Toast.LENGTH_LONG).show()
+ } else {
+ navigateToCamera()
+ }
+ }
+
+ companion object {
+ fun hasPermissions(context: Context) = PERMISSIONS_REQUIRED.all {
+ ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/feature/analysis/src/main/java/com/jaino/analysis/image_input/ChooseToolsDialog.kt b/android/feature/analysis/src/main/java/com/jaino/analysis/image_input/ChooseToolsDialog.kt
new file mode 100644
index 00000000..9c62192e
--- /dev/null
+++ b/android/feature/analysis/src/main/java/com/jaino/analysis/image_input/ChooseToolsDialog.kt
@@ -0,0 +1,31 @@
+package com.jaino.analysis.image_input
+
+import android.content.Context
+import android.os.Bundle
+import com.google.android.material.bottomsheet.BottomSheetDialog
+import com.jaino.analysis.databinding.DialogChooseToolsBinding
+
+class ChooseToolsDialog(
+ context : Context,
+ private val onCameraClick : () -> Unit,
+ private val onAlbumClick : () -> Unit,
+ ): BottomSheetDialog(context) {
+ private val binding by lazy { DialogChooseToolsBinding.inflate(layoutInflater) }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ this.setContentView(binding.root)
+ initViews()
+ }
+
+ private fun initViews(){
+ binding.albumPictureButton.setOnClickListener{
+ onAlbumClick()
+ dismiss()
+ }
+ binding.cameraPictureButton.setOnClickListener{
+ onCameraClick()
+ dismiss()
+ }
+ }
+}
diff --git a/android/feature/analysis/src/main/java/com/jaino/analysis/image_input/ImageInputFragment.kt b/android/feature/analysis/src/main/java/com/jaino/analysis/image_input/ImageInputFragment.kt
new file mode 100644
index 00000000..f5948c28
--- /dev/null
+++ b/android/feature/analysis/src/main/java/com/jaino/analysis/image_input/ImageInputFragment.kt
@@ -0,0 +1,192 @@
+package com.jaino.analysis.image_input
+
+import android.content.Context
+import android.net.Uri
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Toast
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.launch
+import androidx.core.net.toUri
+import androidx.databinding.DataBindingUtil
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import androidx.lifecycle.flowWithLifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.fragment.findNavController
+import androidx.navigation.fragment.navArgs
+import com.jaino.analysis.R
+import com.jaino.analysis.databinding.FragmentImageInputBinding
+import com.jaino.common.extensions.toDateTime
+import com.jaino.common.model.UiEvent
+import com.jaino.common.utils.PickPhotoContract
+import com.jaino.common.widget.ConfirmDialog
+import com.jaino.common.widget.ErrorDialog
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import java.util.*
+
+@AndroidEntryPoint
+class ImageInputFragment : Fragment() {
+
+ private lateinit var singlePhotoPickerLauncher : ActivityResultLauncher
+
+ private var _binding: FragmentImageInputBinding? = null
+ private val binding
+ get() = requireNotNull(_binding) { "binding object is not initialized" }
+
+ private val viewModel : ImageInputViewModel by viewModels()
+ private val args : ImageInputFragmentArgs by navArgs()
+
+ override fun onAttach(context: Context) {
+ super.onAttach(context)
+ singlePhotoPickerLauncher = registerForActivityResult(PickPhotoContract())
+ { imageUri: Uri? ->
+ if (imageUri != null) {
+ viewModel.setImageUri(imageUri.toString())
+ loadImage(imageUri.toString())
+ }
+ }
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ _binding = DataBindingUtil.inflate(
+ inflater, R.layout.fragment_image_input, container, false)
+ binding.lifecycleOwner = viewLifecycleOwner
+ binding.viewModel = viewModel
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ initViews()
+ initViewModelStates()
+ observeData()
+ }
+
+ private fun initViews(){
+ binding.analyzeInputImage.setOnClickListener {
+ showChooseToolsDialog()
+ }
+ // ์๋ฃ ๋ฒํผ ํด๋ฆญ
+ binding.analyzeImageDoneButton.setOnClickListener {
+ showConfirmDialog()
+ }
+ // ๋ค๋ก๊ฐ๊ธฐ ๋ฒํผ ํด๋ฆญ
+ binding.analyzeBackButton.setOnClickListener {
+ navigateToText()
+ }
+ }
+
+ private fun initViewModelStates(){
+ // imageUri ์ ์ฅ
+ val imageUri = args.imageUri
+ if(imageUri.isNotEmpty()){
+ viewModel.setImageUri(imageUri)
+ loadImage(imageUri)
+ }
+ }
+
+ private fun loadImage(uri: String){
+ val inputStream = requireActivity().contentResolver.openInputStream(uri.toUri())
+ if(inputStream != null) {
+ val bytes = ByteArray(inputStream.available())
+ inputStream.read(bytes)
+ val encodedImage: String = Base64.getUrlEncoder().encodeToString(bytes)
+ viewModel.setImageSource(encodedImage)
+ }else{
+ Toast.makeText(requireContext(), "์ฌ์ง์ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค.", Toast.LENGTH_SHORT).show()
+ navigateToText()
+ }
+ inputStream?.close()
+ }
+
+ private fun observeData(){
+ viewModel.analysisId.flowWithLifecycle(viewLifecycleOwner.lifecycle)
+ .onEach { id ->
+ if (id != -1L) {
+ hideLoadingView()
+ navigateToResult(id)
+ }
+ }.launchIn(viewLifecycleOwner.lifecycleScope)
+
+ viewModel.analysisUiEvent.flowWithLifecycle(viewLifecycleOwner.lifecycle)
+ .onEach {
+ when(it){
+ is UiEvent.Failure -> {
+ hideLoadingView()
+ showErrorDialog(it.error)
+ }
+ is UiEvent.Success -> { }
+ }
+ }.launchIn(viewLifecycleOwner.lifecycleScope)
+ }
+
+ private fun showChooseToolsDialog(){
+ ChooseToolsDialog(
+ requireContext(),
+ onCameraClick = { navigateToPermission() },
+ onAlbumClick = {
+ singlePhotoPickerLauncher.launch()
+ }
+ ).show()
+ }
+
+ private fun showConfirmDialog(){
+ ConfirmDialog(
+ requireContext(),
+ "์ ์ถ ํ์๊ฒ ์ต๋๊น?",
+ onDoneButtonClick = {
+ submitSource()
+ showLoadingView()
+ }
+ ).show()
+ }
+
+ private fun showLoadingView(){
+ binding.loadingView.visibility = View.VISIBLE
+ }
+
+ private fun hideLoadingView(){
+ binding.loadingView.visibility = View.GONE
+ }
+
+ private fun showErrorDialog(error: Throwable){
+ ErrorDialog(
+ requireContext(),
+ error = error,
+ onRetryButtonClick = {
+ submitSource()
+ }
+ ).show()
+ }
+
+ private fun submitSource(){
+ viewModel.postAnalysisSource(System.currentTimeMillis().toDateTime(), args.analyzeText)
+ }
+
+ private fun navigateToPermission(){
+ findNavController().navigate(
+ ImageInputFragmentDirections.actionImageInputFragmentToPermissionsFragment()
+ )
+ }
+
+ private fun navigateToResult(analysisId: Long){
+ findNavController().navigate(
+ ImageInputFragmentDirections.actionImageInputFragmentToResultFragment(
+ analysisId
+ )
+ )
+ }
+
+ private fun navigateToText(){
+ findNavController().popBackStack(R.id.textInputFragment, false)
+ }
+}
\ No newline at end of file
diff --git a/android/feature/analysis/src/main/java/com/jaino/analysis/image_input/ImageInputViewModel.kt b/android/feature/analysis/src/main/java/com/jaino/analysis/image_input/ImageInputViewModel.kt
new file mode 100644
index 00000000..cd51bcad
--- /dev/null
+++ b/android/feature/analysis/src/main/java/com/jaino/analysis/image_input/ImageInputViewModel.kt
@@ -0,0 +1,52 @@
+package com.jaino.analysis.image_input
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.jaino.common.flow.EventFlow
+import com.jaino.common.flow.MutableEventFlow
+import com.jaino.common.flow.asEventFlow
+import com.jaino.common.model.UiEvent
+import com.jaino.data.repository.analysis.AnalysisRepository
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class ImageInputViewModel @Inject constructor(
+ private val analysisRepository: AnalysisRepository
+): ViewModel(){
+
+ private val _imageSourceState = MutableStateFlow("")
+
+ private val _imageUri = MutableStateFlow("")
+ val imageUri : StateFlow get() = _imageUri
+
+ private val _analysisId = MutableStateFlow(-1L)
+ val analysisId : StateFlow get() = _analysisId
+
+ private val _analysisUiEvent : MutableEventFlow> = MutableEventFlow()
+ val analysisUiEvent : EventFlow> get() = _analysisUiEvent.asEventFlow()
+
+ fun setImageUri(imageUri: String){
+ _imageUri.value = imageUri
+ }
+ fun setImageSource(analyzeImage: String){
+ _imageSourceState.value = analyzeImage
+ }
+
+ fun postAnalysisSource(date : String, textSource: String){
+ viewModelScope.launch {
+ analysisRepository.postAnalysisSource(
+ date = date,
+ textExpression = textSource,
+ facialExpression = _imageSourceState.value
+ ).onSuccess {
+ _analysisId.value = it.analysisId
+ }.onFailure {
+ _analysisUiEvent.emit(UiEvent.Failure(it))
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/feature/analysis/src/main/java/com/jaino/analysis/result/ResultFragment.kt b/android/feature/analysis/src/main/java/com/jaino/analysis/result/ResultFragment.kt
new file mode 100644
index 00000000..ee114ddd
--- /dev/null
+++ b/android/feature/analysis/src/main/java/com/jaino/analysis/result/ResultFragment.kt
@@ -0,0 +1,148 @@
+package com.jaino.analysis.result
+
+import android.content.ActivityNotFoundException
+import android.content.Context
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.net.toUri
+import androidx.databinding.DataBindingUtil
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import androidx.lifecycle.flowWithLifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.fragment.findNavController
+import androidx.navigation.fragment.navArgs
+import com.jaino.analysis.R
+import com.jaino.analysis.databinding.FragmentResultBinding
+import com.jaino.analysis.result.ui.FeedMessage
+import com.jaino.common.extensions.showToast
+import com.jaino.common.model.UiEvent
+import com.jaino.common.widget.ErrorDialog
+import com.kakao.sdk.common.util.KakaoCustomTabsClient
+import com.kakao.sdk.share.ShareClient
+import com.kakao.sdk.share.WebSharerClient
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import com.kakao.sdk.template.model.*
+import timber.log.Timber
+
+@AndroidEntryPoint
+class ResultFragment : Fragment() {
+
+ private var _binding: FragmentResultBinding? = null
+ private val binding
+ get() = requireNotNull(_binding) { "binding object is not initialized" }
+
+ private val viewModel: ResultViewModel by viewModels()
+ private val args: ResultFragmentArgs by navArgs()
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ _binding =
+ DataBindingUtil.inflate(inflater, R.layout.fragment_result, container, false)
+ binding.lifecycleOwner = viewLifecycleOwner
+ binding.viewModel = viewModel
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ initViews()
+ initViewModelStates()
+ observeData()
+ }
+
+ private fun initViews() {
+ binding.resultBackButton.setOnClickListener {
+ navigateToBack()
+ }
+
+ binding.resultShareButton.setOnClickListener {
+ shareKakaoLink(requireContext(), FeedMessage.createTemplate())
+ }
+ }
+
+ private fun initViewModelStates() {
+ viewModel.getAnalysisResult(args.analysisId)
+ viewModel.getNickname()
+ }
+
+ private fun observeData() {
+ viewModel.analysisResultUiEvent.flowWithLifecycle(viewLifecycleOwner.lifecycle)
+ .onEach {
+ when (it) {
+ is UiEvent.Failure -> {
+ showErrorDialog(it.error)
+ }
+ is UiEvent.Success -> {}
+ }
+ }.launchIn(viewLifecycleOwner.lifecycleScope)
+
+ viewModel.analysisResultUiState.flowWithLifecycle(viewLifecycleOwner.lifecycle)
+ .onEach {
+ if (it.sentiment != "") {
+ setProgress(it.level, it.sentiment)
+ }
+ }.launchIn(viewLifecycleOwner.lifecycleScope)
+ }
+
+ private fun setProgress(level: Float, sentiment: String) {
+ binding.resultProgress.progress = level
+ with(binding.resultProgress) {
+ this.progress = level
+ this.labelText = "$sentiment ${level.toInt()}๋จ๊ณ"
+ }
+ }
+
+ private fun shareKakaoLink(context : Context, feedMessage: FeedTemplate) {
+ if (ShareClient.instance.isKakaoTalkSharingAvailable(context)) {
+ // ์นด์นด์คํก์ผ๋ก ์นด์นด์คํก ๊ณต์ ๊ฐ๋ฅ
+ ShareClient.instance.shareDefault(context, feedMessage) { sharingResult, error ->
+ if (error != null) {
+ context.showToast("์นด์นด์คํก ๊ณต์ ์ ์คํจํ์์ต๋๋ค.")
+ Timber.e(error)
+ } else if (sharingResult != null) {
+ startActivity(sharingResult.intent)
+ }
+ }
+ }
+ else {
+ val sharerUrl = WebSharerClient.instance.makeDefaultUrl(feedMessage)
+ try {
+ KakaoCustomTabsClient.openWithDefault(context, sharerUrl)
+ } catch (e: UnsupportedOperationException) {
+ context.showToast("์ฐ๊ฒฐ ๊ฐ๋ฅํ ๋ธ๋ผ์ฐ์ ๊ฐ ์์ต๋๋ค.")
+ }
+ try {
+ KakaoCustomTabsClient.open(context, sharerUrl)
+ } catch (e: ActivityNotFoundException) {
+ context.showToast("์ฐ๊ฒฐ ๊ฐ๋ฅํ ๋ธ๋ผ์ฐ์ ๊ฐ ์์ต๋๋ค.")
+ }
+ }
+ }
+
+ private fun showErrorDialog(error: Throwable){
+ ErrorDialog(
+ requireContext(),
+ error = error,
+ onRetryButtonClick = {
+ viewModel.getAnalysisResult(args.analysisId)
+ }
+ ).show()
+ }
+
+ private fun navigateToBack(){
+ findNavController().navigate("BeJuRyu://feature/home".toUri())
+ }
+
+ override fun onDestroy() {
+ _binding = null
+ super.onDestroy()
+ }
+}
\ No newline at end of file
diff --git a/android/feature/analysis/src/main/java/com/jaino/analysis/result/ResultViewModel.kt b/android/feature/analysis/src/main/java/com/jaino/analysis/result/ResultViewModel.kt
new file mode 100644
index 00000000..99541d60
--- /dev/null
+++ b/android/feature/analysis/src/main/java/com/jaino/analysis/result/ResultViewModel.kt
@@ -0,0 +1,82 @@
+package com.jaino.analysis.result
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.jaino.common.constant.*
+import com.jaino.common.extensions.toSentimentKor
+import com.jaino.common.flow.EventFlow
+import com.jaino.common.flow.MutableEventFlow
+import com.jaino.common.flow.asEventFlow
+import com.jaino.common.model.UiEvent
+import com.jaino.data.repository.analysis.AnalysisRepository
+import com.jaino.data.repository.user.LocalUserRepository
+import com.jaino.model.analysis.SentimentAnalysis
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class ResultViewModel @Inject constructor(
+ private val repository: AnalysisRepository,
+ private val userRepository: LocalUserRepository
+): ViewModel(){
+
+ private val _analysisResultUiEvent = MutableEventFlow>()
+ val analysisResultUiEvent : EventFlow> get() = _analysisResultUiEvent.asEventFlow()
+
+ private val _analysisResultUiState = MutableStateFlow(SentimentAnalysis())
+ val analysisResultUiState : StateFlow get() = _analysisResultUiState
+
+ private val _nicknameState = MutableStateFlow("")
+ val nicknameState : StateFlow get() = _nicknameState
+
+ // ๊ฐ์ icon state
+ private val _sentimentIconState = MutableStateFlow(0)
+ val sentimentIconState : StateFlow get() = _sentimentIconState
+
+ // ๊ฐ์ content state
+ private val _sentimentContentState = MutableStateFlow("")
+ val sentimentContentState : StateFlow get() = _sentimentContentState
+
+ fun getNickname(){
+ viewModelScope.launch {
+ _nicknameState.value = userRepository.getNickName()
+ }
+ }
+
+ fun getAnalysisResult(analysisId: Long){
+ viewModelScope.launch {
+ repository.getSentimentAnalysis(analysisId = analysisId)
+ .onSuccess {
+ val result = it.copy(sentiment = it.sentiment.toSentimentKor())
+ when (result.sentiment) {
+ SAD -> {
+ _sentimentIconState.value = SAD_ICON // icon
+ _sentimentContentState.value = SAD_CONTENT // content
+ _analysisResultUiState.value = result
+ }
+ HAPPY -> {
+ _sentimentIconState.value = HAPPY_ICON
+ _sentimentContentState.value = HAPPY_CONTENT
+ _analysisResultUiState.value = result
+ }
+ MEDIAN -> {
+ _sentimentIconState.value = MEDIAN_ICON
+ _sentimentContentState.value = MEDIAN_CONTENT
+ _analysisResultUiState.value = result
+ }
+ }
+ }.onFailure {
+ _analysisResultUiEvent.emit(UiEvent.Failure(it))
+ }
+ }
+ }
+
+ companion object{
+ val SAD_ICON = com.jaino.designsystem.R.drawable.sad
+ val HAPPY_ICON = com.jaino.designsystem.R.drawable.smile
+ val MEDIAN_ICON = com.jaino.designsystem.R.drawable.neutral
+ }
+}
\ No newline at end of file
diff --git a/android/feature/analysis/src/main/java/com/jaino/analysis/result/ui/FeedMessage.kt b/android/feature/analysis/src/main/java/com/jaino/analysis/result/ui/FeedMessage.kt
new file mode 100644
index 00000000..786edb57
--- /dev/null
+++ b/android/feature/analysis/src/main/java/com/jaino/analysis/result/ui/FeedMessage.kt
@@ -0,0 +1,25 @@
+package com.jaino.analysis.result.ui
+
+import com.kakao.sdk.template.model.Button
+import com.kakao.sdk.template.model.Content
+import com.kakao.sdk.template.model.FeedTemplate
+import com.kakao.sdk.template.model.Link
+
+object FeedMessage {
+ fun createTemplate(): FeedTemplate {
+ return FeedTemplate(
+ content = Content(
+ title = "Be์ฃผ๋ฅ",
+ description = "์ฌ์ฉ์์ ๊ฐ์ ์ ์ธ๊ณต์ง๋ฅ์ด ๋ถ์ํด์ ์ฃผ๋ฅ๋ฅผ ์ถ์ฒํด์!",
+ imageUrl = "https://github.com/pknu-wap/2023_1_WAT_BeJuRyu/raw/develop_android/image/icon.png?raw=true",
+ link = Link()
+ ),
+ buttons = listOf(
+ Button(
+ title = "์ฃผ๋ฅ ์ถ์ฒ ๋ฐ์ผ๋ฌ ๊ฐ๊ธฐ",
+ link = Link()
+ )
+ )
+ )
+ }
+}
\ No newline at end of file
diff --git a/android/feature/analysis/src/main/java/com/jaino/analysis/text_input/TextInputFragment.kt b/android/feature/analysis/src/main/java/com/jaino/analysis/text_input/TextInputFragment.kt
new file mode 100644
index 00000000..4f4f2fc4
--- /dev/null
+++ b/android/feature/analysis/src/main/java/com/jaino/analysis/text_input/TextInputFragment.kt
@@ -0,0 +1,90 @@
+package com.jaino.analysis.text_input
+
+import android.annotation.SuppressLint
+import android.os.Bundle
+import android.text.Editable
+import android.text.TextWatcher
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Toast
+import androidx.core.net.toUri
+import androidx.databinding.DataBindingUtil
+import androidx.fragment.app.Fragment
+import androidx.navigation.fragment.findNavController
+import com.jaino.analysis.R
+import com.jaino.analysis.databinding.FragmentTextInputBinding
+import usecase.validate.ValidateTextExpression
+
+class TextInputFragment : Fragment() {
+
+ private val expressionUseCase by lazy{ ValidateTextExpression() }
+
+ private var _binding: FragmentTextInputBinding? = null
+ private val binding
+ get() = requireNotNull(_binding) { "binding object is not initialized" }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ _binding = DataBindingUtil.inflate(
+ inflater, R.layout.fragment_text_input, container, false)
+ binding.lifecycleOwner = viewLifecycleOwner
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ initButtons()
+ initViews()
+ }
+
+ private fun initButtons(){
+ binding.analyzeBackButton.setOnClickListener {
+ navigateToHome()
+ }
+
+ binding.analyzeTextDoneButton.setOnClickListener{
+ val text = binding.analyzeTextExpression.text.toString()
+ val result = expressionUseCase(text)
+ if(!result.successful){
+ Toast.makeText(requireContext(), result.errorMessage, Toast.LENGTH_SHORT).show()
+ return@setOnClickListener
+ }
+ navigateToImage(text)
+ }
+ }
+
+ @SuppressLint("SetTextI18n")
+ private fun initViews(){
+ binding.analyzeTextExpression.addTextChangedListener(object : TextWatcher{
+ override fun afterTextChanged(p0: Editable?) {}
+
+ override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
+
+ override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
+ val length = binding.analyzeTextExpression.text.toString().length
+ binding.analyzeTextCounter.text = "$length/10์ ์ด์"
+ }
+ })
+ }
+
+ private fun navigateToHome(){
+ findNavController().navigate("BeJuRyu://feature/home".toUri())
+ }
+
+ private fun navigateToImage(analyzeText: String){
+ findNavController().navigate(
+ TextInputFragmentDirections.actionTextInputFragmentToImageInputFragment(
+ "", analyzeText
+ )
+ )
+ }
+
+ override fun onDestroy() {
+ _binding = null
+ super.onDestroy()
+ }
+}
\ No newline at end of file
diff --git a/android/feature/analysis/src/main/res/layout/dialog_choose_tools.xml b/android/feature/analysis/src/main/res/layout/dialog_choose_tools.xml
new file mode 100644
index 00000000..4006c39a
--- /dev/null
+++ b/android/feature/analysis/src/main/res/layout/dialog_choose_tools.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/feature/analysis/src/main/res/layout/fragment_camera.xml b/android/feature/analysis/src/main/res/layout/fragment_camera.xml
new file mode 100644
index 00000000..75fd7f11
--- /dev/null
+++ b/android/feature/analysis/src/main/res/layout/fragment_camera.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/feature/analysis/src/main/res/layout/fragment_image_input.xml b/android/feature/analysis/src/main/res/layout/fragment_image_input.xml
new file mode 100644
index 00000000..97b2ac7d
--- /dev/null
+++ b/android/feature/analysis/src/main/res/layout/fragment_image_input.xml
@@ -0,0 +1,239 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/feature/analysis/src/main/res/layout/fragment_result.xml b/android/feature/analysis/src/main/res/layout/fragment_result.xml
new file mode 100644
index 00000000..780e3ed0
--- /dev/null
+++ b/android/feature/analysis/src/main/res/layout/fragment_result.xml
@@ -0,0 +1,430 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/feature/analysis/src/main/res/layout/fragment_text_input.xml b/android/feature/analysis/src/main/res/layout/fragment_text_input.xml
new file mode 100644
index 00000000..fa3b5a3b
--- /dev/null
+++ b/android/feature/analysis/src/main/res/layout/fragment_text_input.xml
@@ -0,0 +1,194 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/feature/analysis/src/main/res/navigation/analysis_nav.xml b/android/feature/analysis/src/main/res/navigation/analysis_nav.xml
new file mode 100644
index 00000000..59d3e41f
--- /dev/null
+++ b/android/feature/analysis/src/main/res/navigation/analysis_nav.xml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/feature/analysis/src/test/java/com/jaino/analysis/ExampleUnitTest.kt b/android/feature/analysis/src/test/java/com/jaino/analysis/ExampleUnitTest.kt
new file mode 100644
index 00000000..5b055c5c
--- /dev/null
+++ b/android/feature/analysis/src/test/java/com/jaino/analysis/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.jaino.analysis
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * 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)
+ }
+}
\ No newline at end of file
diff --git a/android/feature/auth/.gitignore b/android/feature/auth/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/android/feature/auth/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/android/feature/auth/build.gradle.kts b/android/feature/auth/build.gradle.kts
new file mode 100644
index 00000000..31113646
--- /dev/null
+++ b/android/feature/auth/build.gradle.kts
@@ -0,0 +1,26 @@
+plugins {
+ id("com.jaino.feature")
+ id("com.jaino.hilt")
+}
+
+android {
+ namespace = "com.jaino.auth"
+
+ defaultConfig {
+ consumerProguardFiles("consumer-rules.pro")
+ }
+}
+
+dependencies {
+ implementation(project(":core:data"))
+ implementation(project(":core:domain"))
+ implementation(project(":core:model"))
+ implementation(project(":core:common"))
+ implementation(project(":core:designsystem"))
+ implementation(libs.timber)
+ implementation(libs.kotlin.datetime)
+ implementation(libs.androidx.fragment)
+ implementation(libs.bundles.androidx)
+ implementation(libs.androidx.lifecycle.runtime)
+ implementation(libs.androidx.lifecycle.livedata)
+}
\ No newline at end of file
diff --git a/android/feature/auth/consumer-rules.pro b/android/feature/auth/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/android/feature/auth/proguard-rules.pro b/android/feature/auth/proguard-rules.pro
new file mode 100644
index 00000000..ff59496d
--- /dev/null
+++ b/android/feature/auth/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.kts.
+#
+# 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/android/feature/auth/src/androidTest/java/com/jaino/auth/ExampleInstrumentedTest.kt b/android/feature/auth/src/androidTest/java/com/jaino/auth/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..206d9112
--- /dev/null
+++ b/android/feature/auth/src/androidTest/java/com/jaino/auth/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.jaino.auth
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.jaino.auth.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/android/feature/auth/src/main/AndroidManifest.xml b/android/feature/auth/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a5918e68
--- /dev/null
+++ b/android/feature/auth/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/android/feature/auth/src/main/java/com/jaino/auth/AuthActivity.kt b/android/feature/auth/src/main/java/com/jaino/auth/AuthActivity.kt
new file mode 100644
index 00000000..96117cdc
--- /dev/null
+++ b/android/feature/auth/src/main/java/com/jaino/auth/AuthActivity.kt
@@ -0,0 +1,89 @@
+package com.jaino.auth
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.widget.Toast
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import androidx.databinding.DataBindingUtil
+import androidx.lifecycle.flowWithLifecycle
+import androidx.lifecycle.lifecycleScope
+import com.jaino.auth.databinding.ActivityAuthBinding
+import com.jaino.data.repository.auth.SocialAuthRepository
+import com.jaino.common.navigation.AppNavigator
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class AuthActivity : AppCompatActivity() {
+
+ @Inject
+ lateinit var appNavigator: AppNavigator
+
+ private val viewModel : AuthViewModel by viewModels()
+
+ @Inject
+ lateinit var socialAuthRepository: SocialAuthRepository
+
+ private var _binding: ActivityAuthBinding? = null
+ private val binding get() = checkNotNull(_binding)
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ _binding = DataBindingUtil.setContentView(this, R.layout.activity_auth)
+ binding.lifecycleOwner = this
+ initButtons()
+ observeData()
+ }
+
+ private fun initButtons(){
+ binding.kakaoSignInButton.setOnClickListener{
+ if(socialAuthRepository.isKakaoTalkLoginAvailable){ // KakaoTalk ์คํ ๊ฐ๋ฅ
+ lifecycleScope.launch {
+ socialAuthRepository.signInByKakaoTalk()
+ .onSuccess {
+ viewModel.executeServiceSignIn(it.token)
+ }
+ .onFailure {
+ Toast.makeText(this@AuthActivity,
+ "์นด์นด์คํก ๋ก๊ทธ์ธ์ ์คํจํ์์ต๋๋ค.", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+ else{
+ lifecycleScope.launch {
+ socialAuthRepository.signInByKakaoAccount()
+ .onSuccess {
+ viewModel.executeServiceSignIn(it.token)
+ }
+ .onFailure {
+ Toast.makeText(this@AuthActivity,
+ "์นด์นด์ค ๊ณ์ ๋ก๊ทธ์ธ์ ์คํจํ์์ต๋๋ค.", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+ }
+ }
+
+ private fun observeData(){
+ viewModel.authUiState.flowWithLifecycle(lifecycle).onEach{
+ when(it){
+ is AuthViewModel.UiEvent.Success -> {
+ startActivity(appNavigator.navigateToHome())
+ }
+ is AuthViewModel.UiEvent.Failure -> {
+ Toast.makeText(this, it.message, Toast.LENGTH_SHORT).show()
+ }
+ }
+ }.launchIn(lifecycleScope)
+ }
+
+ companion object {
+ fun getIntent(context: Context): Intent =
+ Intent(context, AuthActivity::class.java)
+ }
+}
diff --git a/android/feature/auth/src/main/java/com/jaino/auth/AuthViewModel.kt b/android/feature/auth/src/main/java/com/jaino/auth/AuthViewModel.kt
new file mode 100644
index 00000000..eb08ed9f
--- /dev/null
+++ b/android/feature/auth/src/main/java/com/jaino/auth/AuthViewModel.kt
@@ -0,0 +1,35 @@
+package com.jaino.auth
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.jaino.data.repository.auth.AuthRepository
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.launch
+import timber.log.Timber
+import javax.inject.Inject
+
+@HiltViewModel
+class AuthViewModel @Inject constructor(
+ private val repository: AuthRepository
+) : ViewModel() {
+
+ private val _authUiState : MutableSharedFlow = MutableSharedFlow()
+ val authUiState : SharedFlow get() = _authUiState
+ fun executeServiceSignIn(token: String){
+ viewModelScope.launch {
+ repository.signInService(token).onSuccess {
+ _authUiState.emit(UiEvent.Success)
+ }.onFailure {
+ Timber.e(it)
+ _authUiState.emit(UiEvent.Failure(it.message))
+ }
+ }
+ }
+
+ sealed class UiEvent{
+ object Success : UiEvent()
+ data class Failure(val message: String?) : UiEvent()
+ }
+}
\ No newline at end of file
diff --git a/android/feature/auth/src/main/res/layout/activity_auth.xml b/android/feature/auth/src/main/res/layout/activity_auth.xml
new file mode 100644
index 00000000..e94e5beb
--- /dev/null
+++ b/android/feature/auth/src/main/res/layout/activity_auth.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/feature/auth/src/test/java/com/jaino/auth/ExampleUnitTest.kt b/android/feature/auth/src/test/java/com/jaino/auth/ExampleUnitTest.kt
new file mode 100644
index 00000000..dfda4451
--- /dev/null
+++ b/android/feature/auth/src/test/java/com/jaino/auth/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.jaino.auth
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * 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)
+ }
+}
\ No newline at end of file
diff --git a/android/feature/dictionary/.gitignore b/android/feature/dictionary/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/android/feature/dictionary/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/android/feature/dictionary/build.gradle.kts b/android/feature/dictionary/build.gradle.kts
new file mode 100644
index 00000000..6232f967
--- /dev/null
+++ b/android/feature/dictionary/build.gradle.kts
@@ -0,0 +1,26 @@
+plugins {
+ id("com.jaino.feature")
+ id("com.jaino.hilt")
+ alias(libs.plugins.androidx.navigation.safeargs)
+}
+
+android {
+ namespace = "com.jaino.dictionary"
+}
+
+dependencies {
+ implementation(project(":core:data"))
+ implementation(project(":core:domain"))
+ implementation(project(":core:model"))
+ implementation(project(":core:common"))
+ implementation(project(":core:designsystem"))
+ implementation(libs.timber)
+ implementation(libs.kotlin.datetime)
+ implementation(libs.androidx.fragment)
+ implementation(libs.bundles.navigation)
+ implementation(libs.bundles.androidx)
+ implementation(libs.material)
+ implementation(libs.glide)
+ implementation(libs.androidx.lifecycle.runtime)
+ implementation(libs.androidx.lifecycle.livedata)
+}
\ No newline at end of file
diff --git a/android/feature/dictionary/consumer-rules.pro b/android/feature/dictionary/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/android/feature/dictionary/proguard-rules.pro b/android/feature/dictionary/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/android/feature/dictionary/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/android/feature/dictionary/src/androidTest/java/com/jaino/dictionary/ExampleInstrumentedTest.kt b/android/feature/dictionary/src/androidTest/java/com/jaino/dictionary/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..1f63d646
--- /dev/null
+++ b/android/feature/dictionary/src/androidTest/java/com/jaino/dictionary/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.jaino.dictionary
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.jaino.dictionary.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/android/feature/dictionary/src/main/AndroidManifest.xml b/android/feature/dictionary/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a5918e68
--- /dev/null
+++ b/android/feature/dictionary/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/android/feature/dictionary/src/main/java/com/jaino/dictionary/drink_info/DrinkInfoFragment.kt b/android/feature/dictionary/src/main/java/com/jaino/dictionary/drink_info/DrinkInfoFragment.kt
new file mode 100644
index 00000000..846062f9
--- /dev/null
+++ b/android/feature/dictionary/src/main/java/com/jaino/dictionary/drink_info/DrinkInfoFragment.kt
@@ -0,0 +1,113 @@
+package com.jaino.dictionary.drink_info
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.net.toUri
+import androidx.databinding.DataBindingUtil
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import androidx.lifecycle.flowWithLifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.fragment.findNavController
+import androidx.navigation.fragment.navArgs
+import com.jaino.common.model.UiEvent
+import com.jaino.common.widget.ErrorDialog
+import com.jaino.dictionary.R
+import com.jaino.dictionary.databinding.FragmentDrinkInfoBinding
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+@AndroidEntryPoint
+class DrinkInfoFragment : Fragment() {
+
+ private var _binding : FragmentDrinkInfoBinding?= null
+ private val binding get() = requireNotNull(_binding){ "binding object is not initialized" }
+
+ private val viewModel : DrinkInfoViewModel by viewModels()
+ private val args : DrinkInfoFragmentArgs by navArgs()
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ _binding = DataBindingUtil.inflate(inflater, R.layout.fragment_drink_info, container, false)
+ binding.lifecycleOwner = viewLifecycleOwner
+ binding.viewModel = viewModel
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ viewModel.getDrinkData(args.drinkId)
+ initViews()
+ observeData()
+ }
+
+ private fun initViews(){
+ binding.backButton.setOnClickListener {
+ val navigation = findNavController().popBackStack(R.id.drinkListFragment, false)
+ if(!navigation){ // backstack์ drinkList๊ฐ ์๋ ๊ฒฝ์ฐ, ํ์ผ๋ก ์ด๋
+ navigateToHome()
+ }
+ }
+
+ binding.goToHomeButton.setOnClickListener {
+ navigateToHome()
+ }
+
+ binding.goToSearchButton.setOnClickListener {
+ navigateToSearch()
+ }
+
+ binding.drinkReviewCardView.setOnClickListener{
+ navigateToReview()
+ }
+ }
+
+ private fun observeData(){
+ viewModel.drinkInfoEvent.flowWithLifecycle(viewLifecycleOwner.lifecycle)
+ .onEach {
+ when (it) {
+ is UiEvent.Failure -> {
+ showErrorDialog(it.error)
+ }
+
+ is UiEvent.Success -> {}
+ }
+ }.launchIn(viewLifecycleOwner.lifecycleScope)
+ }
+
+ private fun showErrorDialog(error: Throwable){
+ ErrorDialog(
+ requireContext(),
+ error = error,
+ onRetryButtonClick = {
+ viewModel.getDrinkData(args.drinkId)
+ }
+ ).show()
+ }
+
+ private fun navigateToHome(){
+ findNavController().navigate("BeJuRyu://feature/home".toUri())
+ }
+
+ private fun navigateToSearch(){
+ val direction = DrinkInfoFragmentDirections
+ .actionDrinkInfoFragmentToDrinkSearchFragment()
+ findNavController().navigate(direction)
+ }
+
+ private fun navigateToReview(){
+ findNavController()
+ .navigate("BeJuRyu://feature/review/list?drinkId=${args.drinkId}".toUri())
+ }
+
+ override fun onDestroy() {
+ _binding = null
+ super.onDestroy()
+ }
+}
\ No newline at end of file
diff --git a/android/feature/dictionary/src/main/java/com/jaino/dictionary/drink_info/DrinkInfoViewModel.kt b/android/feature/dictionary/src/main/java/com/jaino/dictionary/drink_info/DrinkInfoViewModel.kt
new file mode 100644
index 00000000..903c09f5
--- /dev/null
+++ b/android/feature/dictionary/src/main/java/com/jaino/dictionary/drink_info/DrinkInfoViewModel.kt
@@ -0,0 +1,39 @@
+package com.jaino.dictionary.drink_info
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.jaino.common.flow.EventFlow
+import com.jaino.common.flow.MutableEventFlow
+import com.jaino.common.flow.asEventFlow
+import com.jaino.common.model.UiEvent
+import com.jaino.data.repository.dictionary.DrinksRepository
+import com.jaino.model.dictionary.Drink
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class DrinkInfoViewModel @Inject constructor(
+ private val repository: DrinksRepository
+): ViewModel() {
+
+ private val _drinkInfoEvent : MutableEventFlow> = MutableEventFlow()
+ val drinkInfoEvent : EventFlow> get() = _drinkInfoEvent.asEventFlow()
+
+ private val _drinkInfoUiState : MutableStateFlow = MutableStateFlow(Drink())
+ val drinkInfoUiState : StateFlow get() = _drinkInfoUiState
+
+ fun getDrinkData(id: Long){
+ viewModelScope.launch {
+ repository.getDrinkDataById(id)
+ .onSuccess { drinkData ->
+ _drinkInfoUiState.value = drinkData
+ }
+ .onFailure {
+ _drinkInfoEvent.emit(UiEvent.Failure(it))
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/feature/dictionary/src/main/java/com/jaino/dictionary/drink_list/DrinkListFragment.kt b/android/feature/dictionary/src/main/java/com/jaino/dictionary/drink_list/DrinkListFragment.kt
new file mode 100644
index 00000000..e88cf584
--- /dev/null
+++ b/android/feature/dictionary/src/main/java/com/jaino/dictionary/drink_list/DrinkListFragment.kt
@@ -0,0 +1,141 @@
+package com.jaino.dictionary.drink_list
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.net.toUri
+import androidx.databinding.DataBindingUtil
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import androidx.lifecycle.flowWithLifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.fragment.findNavController
+import androidx.navigation.fragment.navArgs
+import androidx.recyclerview.widget.GridLayoutManager
+import com.jaino.common.model.UiEvent
+import com.jaino.common.model.UiState
+import com.jaino.common.widget.ErrorDialog
+import com.jaino.dictionary.R
+import com.jaino.dictionary.databinding.FragmentDrinkListBinding
+import com.jaino.dictionary.drink_list.adapter.DrinkDataAdapter
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+@AndroidEntryPoint
+class DrinkListFragment : Fragment(){
+
+ private var _binding : FragmentDrinkListBinding?= null
+ private val binding get() = requireNotNull(_binding) { "binding object is not initialized"}
+
+ private lateinit var adapter : DrinkDataAdapter
+
+ private val viewModel : DrinkListViewModel by viewModels()
+ private val args : DrinkListFragmentArgs by navArgs()
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ _binding = DataBindingUtil.inflate(inflater, R.layout.fragment_drink_list, container, false)
+ binding.lifecycleOwner = viewLifecycleOwner
+ binding.viewModel = viewModel
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ initAdapter()
+ observeData()
+ initViews()
+ initViewModelStates()
+ }
+
+ private fun initAdapter(){
+ adapter = DrinkDataAdapter(
+ itemClick = { id ->
+ navigateToInfo(id)
+ }
+ )
+ binding.drinkDataRecyclerView.adapter = adapter
+ binding.drinkDataRecyclerView.layoutManager = GridLayoutManager(requireContext(), 2)
+ }
+
+ private fun observeData(){
+ viewModel.drinkListUiState.flowWithLifecycle(viewLifecycleOwner.lifecycle)
+ .onEach {
+ when(it){
+ is UiState.Success -> {
+ if(it.data.isNotEmpty()) {
+ adapter.submitList(it.data)
+ }
+ else{
+ binding.emptyListCard.visibility = View.VISIBLE
+ }
+ }
+
+ is UiState.Init -> {}
+
+ is UiState.Failure -> {}
+ }
+ }.launchIn(viewLifecycleOwner.lifecycleScope)
+
+ viewModel.drinkListUiEvent.flowWithLifecycle(viewLifecycleOwner.lifecycle)
+ .onEach {
+ when(it){
+ is UiEvent.Failure -> {
+ showErrorDialog(it.error)
+ }
+
+ is UiEvent.Success -> {}
+ }
+ }.launchIn(viewLifecycleOwner.lifecycleScope)
+ }
+
+ private fun initViewModelStates(){
+ if(args.type.isNotBlank()){
+ viewModel.getDrinkListByType(args.type)
+ }
+ else if(args.word.isNotBlank()){
+ viewModel.getDrinkListByWord(args.word)
+ }
+ }
+
+ private fun initViews(){
+ binding.backToSearchButton.setOnClickListener{
+ navigateToSearch()
+ }
+
+ binding.emptyListButton.setOnClickListener {
+ navigateToSearch()
+ }
+ }
+
+ private fun navigateToInfo(id : Long){
+ val direction = DrinkListFragmentDirections.actionDrinkListFragmentToDrinkInfoFragment(id)
+ findNavController().navigate(direction)
+ }
+
+ private fun navigateToSearch(){
+ val direction = DrinkListFragmentDirections
+ .actionDrinkListFragmentToDrinkSearchFragment()
+ findNavController().navigate(direction)
+ }
+
+ private fun showErrorDialog(error: Throwable){
+ ErrorDialog(
+ requireContext(),
+ error = error,
+ onRetryButtonClick = {
+ initViewModelStates()
+ }
+ ).show()
+ }
+
+ override fun onDestroy() {
+ _binding = null
+ super.onDestroy()
+ }
+}
\ No newline at end of file
diff --git a/android/feature/dictionary/src/main/java/com/jaino/dictionary/drink_list/DrinkListViewModel.kt b/android/feature/dictionary/src/main/java/com/jaino/dictionary/drink_list/DrinkListViewModel.kt
new file mode 100644
index 00000000..82bb5530
--- /dev/null
+++ b/android/feature/dictionary/src/main/java/com/jaino/dictionary/drink_list/DrinkListViewModel.kt
@@ -0,0 +1,55 @@
+package com.jaino.dictionary.drink_list
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.jaino.common.extensions.toTypedEng
+import com.jaino.common.flow.EventFlow
+import com.jaino.common.flow.MutableEventFlow
+import com.jaino.common.flow.asEventFlow
+import com.jaino.common.model.UiEvent
+import com.jaino.common.model.UiState
+import com.jaino.data.repository.dictionary.DrinksRepository
+import com.jaino.model.dictionary.Drink
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class DrinkListViewModel @Inject constructor(
+ private val repository : DrinksRepository
+): ViewModel() {
+
+ private val _drinkListUiEvent : MutableEventFlow> = MutableEventFlow()
+ val drinkListUiEvent : EventFlow> get() = _drinkListUiEvent.asEventFlow()
+
+ private val _drinkListUiState : MutableStateFlow>> = MutableStateFlow(UiState.Init)
+ val drinkListUiState : StateFlow>> get() = _drinkListUiState
+
+ val searchWord : MutableStateFlow = MutableStateFlow("")
+
+ fun getDrinkListByWord(word: String){
+ viewModelScope.launch {
+ repository.getDrinkListByName(word)
+ .onSuccess {
+ _drinkListUiState.value = UiState.Success(it)
+ searchWord.value = word
+ }
+ .onFailure {
+ _drinkListUiState.value = UiState.Failure(it.message)
+ }
+ }
+ }
+ fun getDrinkListByType(type: String){
+ viewModelScope.launch {
+ repository.getDrinkListByType(type.toTypedEng())
+ .onSuccess {
+ _drinkListUiState.value = UiState.Success(it)
+ }
+ .onFailure {
+ _drinkListUiEvent.emit(UiEvent.Failure(it))
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/feature/dictionary/src/main/java/com/jaino/dictionary/drink_list/adapter/DrinkDataAdapter.kt b/android/feature/dictionary/src/main/java/com/jaino/dictionary/drink_list/adapter/DrinkDataAdapter.kt
new file mode 100644
index 00000000..4a5c58d6
--- /dev/null
+++ b/android/feature/dictionary/src/main/java/com/jaino/dictionary/drink_list/adapter/DrinkDataAdapter.kt
@@ -0,0 +1,45 @@
+package com.jaino.dictionary.drink_list.adapter
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import androidx.recyclerview.widget.RecyclerView
+import com.jaino.dictionary.databinding.ItemDrinkListBinding
+import com.jaino.model.dictionary.Drink
+
+class DrinkDataAdapter(
+ private val itemClick : (Long) -> Unit
+) : ListAdapter(callback) {
+
+ companion object{
+ val callback = object : DiffUtil.ItemCallback(){
+ override fun areItemsTheSame(oldItem: Drink, newItem: Drink): Boolean {
+ return oldItem == newItem
+ }
+
+ override fun areContentsTheSame(oldItem: Drink, newItem: Drink): Boolean {
+ return oldItem.name == newItem.name
+ }
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DrinkDataViewHolder {
+ return DrinkDataViewHolder(ItemDrinkListBinding.inflate(
+ LayoutInflater.from(parent.context), parent, false))
+ }
+
+ override fun onBindViewHolder(holder: DrinkDataViewHolder, position: Int) {
+ holder.bind(currentList[position])
+ }
+
+ inner class DrinkDataViewHolder(private val binding: ItemDrinkListBinding)
+ : RecyclerView.ViewHolder(binding.root){
+ fun bind(item : Drink){
+ binding.item = item
+ binding.drinkDataItemCardView.setOnClickListener {
+ itemClick(item.id)
+ }
+ }
+ }
+}
diff --git a/android/feature/dictionary/src/main/java/com/jaino/dictionary/drink_search/DrinkSearchFragment.kt b/android/feature/dictionary/src/main/java/com/jaino/dictionary/drink_search/DrinkSearchFragment.kt
new file mode 100644
index 00000000..d41b95a9
--- /dev/null
+++ b/android/feature/dictionary/src/main/java/com/jaino/dictionary/drink_search/DrinkSearchFragment.kt
@@ -0,0 +1,83 @@
+package com.jaino.dictionary.drink_search
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.inputmethod.EditorInfo
+import androidx.core.net.toUri
+import androidx.databinding.DataBindingUtil
+import androidx.fragment.app.Fragment
+import androidx.navigation.fragment.findNavController
+import com.google.android.material.chip.Chip
+import com.jaino.common.navigation.AppNavigator
+import com.jaino.dictionary.R
+import com.jaino.dictionary.databinding.FragmentDrinkSearchBinding
+import dagger.hilt.android.AndroidEntryPoint
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class DrinkSearchFragment : Fragment() {
+
+ private var _binding : FragmentDrinkSearchBinding ?= null
+ private val binding get() = requireNotNull(_binding) { "binding object is not initialized" }
+
+ @Inject
+ lateinit var navigator: AppNavigator
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ _binding = DataBindingUtil.inflate(inflater, R.layout.fragment_drink_search, container, false)
+ binding.lifecycleOwner = viewLifecycleOwner
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ initView()
+ }
+
+ private fun initView(){
+ binding.backToAnalyzeButton.setOnClickListener{
+ navigateToHome()
+ }
+
+ binding.searchEditTextView.setOnEditorActionListener { textView, actionId, _ ->
+ if(actionId == EditorInfo.IME_ACTION_SEARCH && textView.text.isNotBlank()){
+ navigateToDrinkList(
+ word = textView.text.toString(),
+ type = ""
+ )
+ true
+ }
+ else false
+ }
+
+ binding.drinkChipGroup.setOnCheckedStateChangeListener{ group, checkedIds ->
+ // single selection
+ val drinkCategory = group.findViewById(checkedIds.first()).text.toString()
+ navigateToDrinkList(
+ word = "",
+ type = drinkCategory
+ )
+ }
+ }
+
+ private fun navigateToDrinkList(word: String, type: String){
+ val direction = DrinkSearchFragmentDirections
+ .actionDrinkSearchFragmentToDrinkListFragment(word, type)
+ findNavController().navigate(direction)
+ }
+
+ private fun navigateToHome(){
+ findNavController().navigate("BeJuRyu://feature/home".toUri())
+ }
+
+ override fun onDestroy() {
+ _binding = null
+ super.onDestroy()
+ }
+}
diff --git a/android/feature/dictionary/src/main/res/layout/fragment_drink_info.xml b/android/feature/dictionary/src/main/res/layout/fragment_drink_info.xml
new file mode 100644
index 00000000..718d24aa
--- /dev/null
+++ b/android/feature/dictionary/src/main/res/layout/fragment_drink_info.xml
@@ -0,0 +1,364 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/feature/dictionary/src/main/res/layout/fragment_drink_list.xml b/android/feature/dictionary/src/main/res/layout/fragment_drink_list.xml
new file mode 100644
index 00000000..aacc3537
--- /dev/null
+++ b/android/feature/dictionary/src/main/res/layout/fragment_drink_list.xml
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/feature/dictionary/src/main/res/layout/fragment_drink_search.xml b/android/feature/dictionary/src/main/res/layout/fragment_drink_search.xml
new file mode 100644
index 00000000..0b06d7b3
--- /dev/null
+++ b/android/feature/dictionary/src/main/res/layout/fragment_drink_search.xml
@@ -0,0 +1,298 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/feature/dictionary/src/main/res/layout/item_drink_list.xml b/android/feature/dictionary/src/main/res/layout/item_drink_list.xml
new file mode 100644
index 00000000..17f3e700
--- /dev/null
+++ b/android/feature/dictionary/src/main/res/layout/item_drink_list.xml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/feature/dictionary/src/main/res/navigation/dictionary_nav.xml b/android/feature/dictionary/src/main/res/navigation/dictionary_nav.xml
new file mode 100644
index 00000000..143be535
--- /dev/null
+++ b/android/feature/dictionary/src/main/res/navigation/dictionary_nav.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/feature/dictionary/src/test/java/com/jaino/dictionary/ExampleUnitTest.kt b/android/feature/dictionary/src/test/java/com/jaino/dictionary/ExampleUnitTest.kt
new file mode 100644
index 00000000..ebcb8e14
--- /dev/null
+++ b/android/feature/dictionary/src/test/java/com/jaino/dictionary/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.jaino.dictionary
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * 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)
+ }
+}
\ No newline at end of file
diff --git a/android/feature/home/.gitignore b/android/feature/home/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/android/feature/home/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/android/feature/home/build.gradle.kts b/android/feature/home/build.gradle.kts
new file mode 100644
index 00000000..97b0df84
--- /dev/null
+++ b/android/feature/home/build.gradle.kts
@@ -0,0 +1,26 @@
+plugins {
+ id("com.jaino.feature")
+ id("com.jaino.hilt")
+}
+
+android {
+ namespace = "com.jaino.home"
+
+ defaultConfig {
+ consumerProguardFiles("consumer-rules.pro")
+ }
+}
+
+dependencies {
+ implementation(project(":core:data"))
+ implementation(project(":core:model"))
+ implementation(project(":core:common"))
+ implementation(project(":core:designsystem"))
+ implementation(libs.bundles.androidx)
+ implementation(libs.androidx.lifecycle.runtime)
+ implementation(libs.androidx.fragment)
+ implementation(libs.bundles.navigation)
+ implementation(libs.material)
+ implementation(libs.timber)
+ implementation(libs.systemUiController)
+}
\ No newline at end of file
diff --git a/android/feature/home/consumer-rules.pro b/android/feature/home/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/android/feature/home/proguard-rules.pro b/android/feature/home/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/android/feature/home/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/android/feature/home/src/androidTest/java/com/jaino/home/ExampleInstrumentedTest.kt b/android/feature/home/src/androidTest/java/com/jaino/home/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..5a01f624
--- /dev/null
+++ b/android/feature/home/src/androidTest/java/com/jaino/home/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.jaino.home
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.jaino.database.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/android/feature/home/src/main/AndroidManifest.xml b/android/feature/home/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a5918e68
--- /dev/null
+++ b/android/feature/home/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/android/feature/home/src/main/java/com/jaino/home/HomeFragment.kt b/android/feature/home/src/main/java/com/jaino/home/HomeFragment.kt
new file mode 100644
index 00000000..2931a085
--- /dev/null
+++ b/android/feature/home/src/main/java/com/jaino/home/HomeFragment.kt
@@ -0,0 +1,152 @@
+package com.jaino.home
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.net.toUri
+import androidx.databinding.DataBindingUtil
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import androidx.core.content.ContextCompat.getColor
+import androidx.lifecycle.flowWithLifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.fragment.findNavController
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.jaino.home.adapter.HomeRankAdapter
+import com.jaino.common.model.UiEvent
+import com.jaino.common.widget.ErrorDialog
+import com.jaino.home.databinding.FragmentHomeBinding
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import land.sungbin.systemuicontroller.setStatusBarColor
+
+@AndroidEntryPoint
+class HomeFragment : Fragment() {
+
+ private var _binding: FragmentHomeBinding? = null
+ private val binding
+ get() = requireNotNull(_binding) { "binding object is not initialized" }
+
+ private val viewModel : HomeViewModel by viewModels()
+
+ private lateinit var adapter : HomeRankAdapter
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ _binding = DataBindingUtil.inflate(inflater, R.layout.fragment_home, container, false)
+ binding.lifecycleOwner = viewLifecycleOwner
+ binding.viewModel = viewModel
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ initAdapter()
+ observeData()
+ initViews()
+ initViewModelStates()
+ }
+
+ private fun initViewModelStates(){
+ viewModel.getRankingList()
+ }
+
+ private fun initAdapter(){
+ adapter = HomeRankAdapter(
+ itemClick = {
+ navigateToDrinkInfo(it)
+ }
+ )
+ binding.rankRecyclerView.adapter = adapter
+ binding.rankRecyclerView.layoutManager =
+ LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false)
+ }
+
+ private fun observeData(){
+ viewModel.homeUiState.flowWithLifecycle(viewLifecycleOwner.lifecycle)
+ .onEach {
+ if(it.isNotEmpty()){
+ adapter.submitList(it)
+ }
+ }.launchIn(viewLifecycleOwner.lifecycleScope)
+
+ viewModel.homeUiEvent.flowWithLifecycle(viewLifecycleOwner.lifecycle)
+ .onEach {
+ when(it){
+ is UiEvent.Failure -> {
+ showErrorDialog(it.error)
+ }
+ is UiEvent.Success -> {
+
+ }
+ }
+ }.launchIn(viewLifecycleOwner.lifecycleScope)
+
+ viewModel.rankingTag.flowWithLifecycle(viewLifecycleOwner.lifecycle)
+ .onEach {
+ when(it){
+ requireContext().getString(com.jaino.designsystem.R.string.most_reviewed) -> {
+ binding.mostReviewedChip.isChecked = true
+ }
+
+ requireContext().getString(com.jaino.designsystem.R.string.most_reviewed) -> {
+ binding.highestRatedChip.isChecked = true
+ }
+ }
+ }.launchIn(viewLifecycleOwner.lifecycleScope)
+ }
+
+ private fun initViews(){
+ binding.goToAnalyzeButton.setOnClickListener{
+ navigateToTextInput()
+ }
+
+ binding.goToSearchButton.setOnClickListener{
+ navigateToSearch()
+ }
+
+ binding.goToSettingButton.setOnClickListener{
+ navigateToSetting()
+ }
+ setStatusBarColor(getColor(requireContext(), com.jaino.designsystem.R.color.purple))
+ }
+
+ private fun showErrorDialog(error: Throwable){
+ ErrorDialog(
+ requireContext(),
+ error = error,
+ onRetryButtonClick = {
+ viewModel.getRankingList()
+ }
+ ).show()
+ }
+
+
+ private fun navigateToTextInput(){
+ findNavController().navigate("BeJuRyu://feature/analysis/text".toUri())
+ }
+
+ private fun navigateToSetting(){
+ findNavController().navigate("BeJuRyu://feature/setting".toUri())
+ }
+
+ private fun navigateToSearch(){
+ findNavController().navigate("BeJuRyu://feature/dictionary".toUri())
+ }
+
+ private fun navigateToDrinkInfo(id: Long){
+ findNavController().navigate(
+ "BeJuRyu://feature/dictionary/info?drinkId=$id".toUri()
+ )
+ }
+
+ override fun onDestroy() {
+ _binding = null
+ super.onDestroy()
+ }
+}
\ No newline at end of file
diff --git a/android/feature/home/src/main/java/com/jaino/home/HomeViewModel.kt b/android/feature/home/src/main/java/com/jaino/home/HomeViewModel.kt
new file mode 100644
index 00000000..7aecf1c5
--- /dev/null
+++ b/android/feature/home/src/main/java/com/jaino/home/HomeViewModel.kt
@@ -0,0 +1,70 @@
+package com.jaino.home
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.jaino.common.flow.EventFlow
+import com.jaino.common.flow.MutableEventFlow
+import com.jaino.common.model.UiEvent
+import com.jaino.data.repository.rank.RankRepository
+import com.jaino.model.rank.Rank
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class HomeViewModel @Inject constructor(
+ private val repository: RankRepository
+) : ViewModel() {
+
+ private val _homeUiEvent : MutableEventFlow> = MutableEventFlow>()
+ val homeUiEvent : EventFlow> get() = _homeUiEvent
+
+ private val _homeUiState : MutableStateFlow> = MutableStateFlow(emptyList())
+ val homeUiState : StateFlow> get() = _homeUiState
+
+ private val _rankingTag : MutableStateFlow = MutableStateFlow(MOST_REVIEWED)
+ val rankingTag: StateFlow get() = _rankingTag.asStateFlow()
+
+ fun getRankingList(){
+ when(_rankingTag.value){
+ MOST_REVIEWED -> {
+ getMostReviewedList()
+ }
+ HIGHEST_RATED -> {
+ getHighestRatedList()
+ }
+ }
+ }
+
+ fun getHighestRatedList(){
+ viewModelScope.launch {
+ _rankingTag.value = HIGHEST_RATED
+ repository.getHighestRatedList()
+ .onSuccess {
+ _homeUiState.value = it
+ }
+ .onFailure {
+ _homeUiEvent.emit(UiEvent.Failure(it))
+ }
+ }
+ }
+
+ fun getMostReviewedList(){
+ viewModelScope.launch {
+ _rankingTag.value = MOST_REVIEWED
+ repository.getMostReviewedList()
+ .onSuccess {
+ _homeUiState.value = it
+ }
+ .onFailure {
+ _homeUiEvent.emit(UiEvent.Failure(it))
+ }
+ }
+ }
+
+ companion object{
+ const val MOST_REVIEWED = "๋ฆฌ๋ทฐ ๋ง์ ์"
+ const val HIGHEST_RATED = "ํ์ ๋์ ์"
+ }
+}
\ No newline at end of file
diff --git a/android/feature/home/src/main/java/com/jaino/home/adapter/HomeRankAdapter.kt b/android/feature/home/src/main/java/com/jaino/home/adapter/HomeRankAdapter.kt
new file mode 100644
index 00000000..c326b101
--- /dev/null
+++ b/android/feature/home/src/main/java/com/jaino/home/adapter/HomeRankAdapter.kt
@@ -0,0 +1,45 @@
+package com.jaino.home.adapter
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import androidx.recyclerview.widget.RecyclerView
+import com.jaino.home.databinding.ItemRankBinding
+import com.jaino.model.rank.Rank
+
+class HomeRankAdapter(
+ private val itemClick : (Long) -> Unit
+) : ListAdapter(callback) {
+
+ companion object{
+ val callback = object : DiffUtil.ItemCallback(){
+ override fun areItemsTheSame(oldItem: Rank, newItem: Rank): Boolean {
+ return oldItem == newItem
+ }
+
+ override fun areContentsTheSame(oldItem: Rank, newItem: Rank): Boolean {
+ return oldItem.name == newItem.name
+ }
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RankViewHolder {
+ return RankViewHolder(ItemRankBinding.inflate(
+ LayoutInflater.from(parent.context), parent, false))
+ }
+
+ override fun onBindViewHolder(holder: RankViewHolder, position: Int) {
+ holder.bind(currentList[position])
+ }
+
+ inner class RankViewHolder(private val binding: ItemRankBinding)
+ : RecyclerView.ViewHolder(binding.root){
+ fun bind(item : Rank){
+ binding.item = item
+ binding.root.setOnClickListener {
+ itemClick(item.id)
+ }
+ }
+ }
+}
diff --git a/android/feature/home/src/main/res/layout/fragment_home.xml b/android/feature/home/src/main/res/layout/fragment_home.xml
new file mode 100644
index 00000000..6c682005
--- /dev/null
+++ b/android/feature/home/src/main/res/layout/fragment_home.xml
@@ -0,0 +1,313 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/feature/home/src/main/res/layout/item_rank.xml b/android/feature/home/src/main/res/layout/item_rank.xml
new file mode 100644
index 00000000..776a7253
--- /dev/null
+++ b/android/feature/home/src/main/res/layout/item_rank.xml
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/feature/home/src/main/res/navigation/home_nav.xml b/android/feature/home/src/main/res/navigation/home_nav.xml
new file mode 100644
index 00000000..6dedd9ef
--- /dev/null
+++ b/android/feature/home/src/main/res/navigation/home_nav.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/feature/home/src/test/java/com/jaino/home/ExampleUnitTest.kt b/android/feature/home/src/test/java/com/jaino/home/ExampleUnitTest.kt
new file mode 100644
index 00000000..39dc846c
--- /dev/null
+++ b/android/feature/home/src/test/java/com/jaino/home/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.jaino.home
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * 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)
+ }
+}
\ No newline at end of file
diff --git a/android/feature/review/.gitignore b/android/feature/review/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/android/feature/review/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/android/feature/review/build.gradle.kts b/android/feature/review/build.gradle.kts
new file mode 100644
index 00000000..d95b63e9
--- /dev/null
+++ b/android/feature/review/build.gradle.kts
@@ -0,0 +1,26 @@
+plugins {
+ id("com.jaino.feature")
+ id("com.jaino.hilt")
+ alias(libs.plugins.androidx.navigation.safeargs)
+}
+
+android {
+ namespace = "com.jaino.review"
+}
+
+dependencies {
+ implementation(project(":core:data"))
+ implementation(project(":core:domain"))
+ implementation(project(":core:model"))
+ implementation(project(":core:common"))
+ implementation(project(":core:designsystem"))
+ implementation(libs.timber)
+ implementation(libs.kotlin.datetime)
+ implementation(libs.androidx.fragment)
+ implementation(libs.bundles.navigation)
+ implementation(libs.bundles.androidx)
+ implementation(libs.material)
+ implementation(libs.glide)
+ implementation(libs.androidx.lifecycle.runtime)
+ implementation(libs.androidx.lifecycle.livedata)
+}
\ No newline at end of file
diff --git a/android/feature/review/consumer-rules.pro b/android/feature/review/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/android/feature/review/proguard-rules.pro b/android/feature/review/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/android/feature/review/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/android/feature/review/src/androidTest/java/com/jaino/review/ExampleInstrumentedTest.kt b/android/feature/review/src/androidTest/java/com/jaino/review/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..f816d2df
--- /dev/null
+++ b/android/feature/review/src/androidTest/java/com/jaino/review/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.jaino.review
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.jaino.review.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/android/feature/review/src/main/AndroidManifest.xml b/android/feature/review/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a5918e68
--- /dev/null
+++ b/android/feature/review/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/android/feature/review/src/main/java/com/jaino/review/review_input/ReviewInputFragment.kt b/android/feature/review/src/main/java/com/jaino/review/review_input/ReviewInputFragment.kt
new file mode 100644
index 00000000..a24e261d
--- /dev/null
+++ b/android/feature/review/src/main/java/com/jaino/review/review_input/ReviewInputFragment.kt
@@ -0,0 +1,120 @@
+package com.jaino.review.review_input
+
+import android.annotation.SuppressLint
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.net.toUri
+import androidx.databinding.DataBindingUtil
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import androidx.lifecycle.flowWithLifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.fragment.findNavController
+import androidx.navigation.fragment.navArgs
+import com.jaino.common.model.UiEvent
+import com.jaino.common.navigation.AppNavigator
+import com.jaino.common.widget.ConfirmDialog
+import com.jaino.common.widget.ErrorDialog
+import com.jaino.review.R
+import com.jaino.review.databinding.FragmentReviewInputBinding
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class ReviewInputFragment : Fragment(){
+
+ private var _binding : FragmentReviewInputBinding? = null
+ private val binding get() = requireNotNull(_binding){ "binding object is not initialized" }
+
+ @Inject
+ lateinit var appNavigator: AppNavigator
+
+ private val viewModel : ReviewInputViewModel by viewModels()
+ private val args : ReviewInputFragmentArgs by navArgs()
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ _binding = DataBindingUtil.inflate(inflater, R.layout.fragment_review_input, container, false)
+ binding.viewModel = viewModel
+ binding.lifecycleOwner = viewLifecycleOwner
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ initViews()
+ initViewModelStates()
+ observeData()
+ }
+
+ private fun initViews(){
+ binding.backButton.setOnClickListener {
+ navigateToList()
+ }
+
+ binding.reviewPostButton.setOnClickListener {
+ showConfirmDialog()
+ }
+ }
+
+ @SuppressLint("SetTextI18n")
+ private fun observeData(){
+ viewModel.reviewInputEvent.flowWithLifecycle(viewLifecycleOwner.lifecycle)
+ .onEach {
+ when(it){
+ is UiEvent.Success -> {
+ navigateToList()
+ }
+
+ is UiEvent.Failure -> {
+ showErrorDialog(it.error)
+ }
+ }
+ }.launchIn(viewLifecycleOwner.lifecycleScope)
+
+ viewModel.reviewContent.flowWithLifecycle(viewLifecycleOwner.lifecycle)
+ .onEach {
+ binding.reviewTextCounter.text = "${it.length}/10์ ์ด์"
+ }.launchIn(viewLifecycleOwner.lifecycleScope)
+ }
+
+ private fun initViewModelStates(){
+ viewModel.getDrinkInfo(args.drinkId)
+ }
+
+ private fun showConfirmDialog(){
+ ConfirmDialog(
+ requireContext(),
+ "์์ฑํ ํ๊ธฐ๋ฅผ ๋ฑ๋กํ ๊น์?",
+ onDoneButtonClick = {
+ viewModel.postReview(args.drinkId)
+ }
+ ).show()
+ }
+
+ private fun showErrorDialog(error: Throwable){
+ ErrorDialog(
+ requireContext(),
+ error = error,
+ onRetryButtonClick = { } // post, get ๋๊ฐ์ง call์ด ์กด์ฌ,
+ ).show()
+ }
+
+ private fun navigateToList(){
+ val direction = ReviewInputFragmentDirections
+ .actionReviewInputFragmentToReviewListFragment(args.drinkId)
+ findNavController().navigate(direction)
+ }
+
+ override fun onDestroy() {
+ _binding = null
+ super.onDestroy()
+ }
+}
\ No newline at end of file
diff --git a/android/feature/review/src/main/java/com/jaino/review/review_input/ReviewInputViewModel.kt b/android/feature/review/src/main/java/com/jaino/review/review_input/ReviewInputViewModel.kt
new file mode 100644
index 00000000..07f36704
--- /dev/null
+++ b/android/feature/review/src/main/java/com/jaino/review/review_input/ReviewInputViewModel.kt
@@ -0,0 +1,64 @@
+package com.jaino.review.review_input
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.jaino.common.extensions.toDateTime
+import com.jaino.common.flow.EventFlow
+import com.jaino.common.flow.MutableEventFlow
+import com.jaino.common.flow.asEventFlow
+import com.jaino.common.model.UiEvent
+import com.jaino.data.repository.review.ReviewRepository
+import com.jaino.data.repository.user.LocalUserRepository
+import com.jaino.data.model.review.ReviewRequest
+import com.jaino.data.repository.dictionary.DrinksRepository
+import com.jaino.model.dictionary.Drink
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class ReviewInputViewModel @Inject constructor(
+ private val repository : ReviewRepository,
+ private val userRepository: LocalUserRepository,
+ private val drinksRepository: DrinksRepository
+): ViewModel() {
+
+ private val _reviewInputEvent : MutableEventFlow> = MutableEventFlow>()
+ val reviewInputEvent : EventFlow> get() = _reviewInputEvent.asEventFlow()
+
+ private val _drinkUiState = MutableStateFlow(Drink())
+ val drinkUiState : StateFlow get() = _drinkUiState
+
+ val reviewContent : MutableStateFlow = MutableStateFlow("")
+ val ratingCount : MutableStateFlow = MutableStateFlow(0.0f)
+
+ fun postReview(drinkId : Long){
+ viewModelScope.launch {
+ repository.postReview(
+ drinkId,
+ ReviewRequest(
+ userRepository.getUserId(), reviewContent.value,
+ ratingCount.value.toInt(), System.currentTimeMillis().toDateTime()
+ )
+ ).onSuccess {
+ _reviewInputEvent.emit(UiEvent.Success(it))
+ }.onFailure {
+ _reviewInputEvent.emit(UiEvent.Failure(it))
+ }
+ }
+ }
+
+ fun getDrinkInfo(drinkId : Long){
+ viewModelScope.launch {
+ drinksRepository.getDrinkDataById(drinkId)
+ .onSuccess { drinkData ->
+ _drinkUiState.value = drinkData
+ }
+ .onFailure {
+ _reviewInputEvent.emit(UiEvent.Failure(it))
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/feature/review/src/main/java/com/jaino/review/review_list/ReviewListFragment.kt b/android/feature/review/src/main/java/com/jaino/review/review_list/ReviewListFragment.kt
new file mode 100644
index 00000000..2f419143
--- /dev/null
+++ b/android/feature/review/src/main/java/com/jaino/review/review_list/ReviewListFragment.kt
@@ -0,0 +1,145 @@
+package com.jaino.review.review_list
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.net.toUri
+import androidx.databinding.DataBindingUtil
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import androidx.lifecycle.flowWithLifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.fragment.findNavController
+import androidx.navigation.fragment.navArgs
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.jaino.common.model.UiEvent
+import com.jaino.common.model.UiState
+import com.jaino.common.widget.ErrorDialog
+import com.jaino.review.R
+import com.jaino.review.databinding.FragmentReviewListBinding
+import com.jaino.review.review_list.adapter.ReviewAdapter
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+@AndroidEntryPoint
+class ReviewListFragment : Fragment() {
+
+ private var _binding : FragmentReviewListBinding? = null
+ private val binding get() = requireNotNull( _binding){ "binding object is not initialized" }
+
+ private val viewModel : ReviewListViewModel by viewModels()
+
+ private val args : ReviewListFragmentArgs by navArgs()
+
+ private lateinit var adapter : ReviewAdapter
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ _binding = DataBindingUtil
+ .inflate(inflater, R.layout.fragment_review_list, container, false)
+ binding.lifecycleOwner = viewLifecycleOwner
+ binding.viewModel = viewModel
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ observeData()
+ initViewModelStates()
+ initViews()
+ initAdapter()
+ }
+
+ private fun initViewModelStates(){
+ viewModel.getReviewList(args.drinkId)
+ viewModel.getDrinkInfo(args.drinkId)
+ }
+
+ private fun initViews(){
+ binding.writeReviewButton.setOnClickListener {
+ navigateToInputReview()
+ }
+
+ binding.backButton.setOnClickListener {
+ navigateToDrinkInfo()
+ }
+
+ binding.goToHomeButton.setOnClickListener {
+ navigateToHome()
+ }
+
+ binding.emptyReviewButton.setOnClickListener{
+ navigateToInputReview()
+ }
+ }
+
+ private fun initAdapter(){
+ adapter = ReviewAdapter()
+ binding.drinkReviewList.adapter = adapter
+ binding.drinkReviewList.layoutManager = LinearLayoutManager(requireContext())
+ }
+
+ private fun observeData(){
+ viewModel.reviewEvent.flowWithLifecycle(viewLifecycleOwner.lifecycle)
+ .onEach {
+ when(it){
+ is UiEvent.Failure -> {
+ showErrorDialog(it.error)
+ }
+ is UiEvent.Success -> { }
+ }
+ }.launchIn(viewLifecycleOwner.lifecycleScope)
+
+ viewModel.reviewItem.flowWithLifecycle(viewLifecycleOwner.lifecycle)
+ .onEach {
+ when(it){
+ is UiState.Init -> { }
+ is UiState.Success -> {
+ if(it.data.isNotEmpty()) {
+ adapter.submitList(it.data)
+ }
+ else{
+ binding.emptyReviewCard.visibility = View.VISIBLE
+ }
+ }
+ is UiState.Failure -> { }
+ }
+ }.launchIn(viewLifecycleOwner.lifecycleScope)
+ }
+
+ private fun showErrorDialog(error: Throwable){
+ ErrorDialog(
+ requireContext(),
+ error = error,
+ onRetryButtonClick = {
+ initViewModelStates()
+ }
+ ).show()
+ }
+
+ private fun navigateToDrinkInfo(){
+ findNavController().navigate(
+ "BeJuRyu://feature/dictionary/info?drinkId=${args.drinkId}".toUri()
+ )
+ }
+
+ private fun navigateToInputReview(){
+ val direction = ReviewListFragmentDirections
+ .actionReviewListFragmentToReviewInputFragment(args.drinkId)
+ findNavController().navigate(direction)
+ }
+
+ private fun navigateToHome(){
+ findNavController().navigate("BeJuRyu://feature/home".toUri())
+ }
+
+ override fun onDestroy() {
+ _binding = null
+ super.onDestroy()
+ }
+}
\ No newline at end of file
diff --git a/android/feature/review/src/main/java/com/jaino/review/review_list/ReviewListViewModel.kt b/android/feature/review/src/main/java/com/jaino/review/review_list/ReviewListViewModel.kt
new file mode 100644
index 00000000..1664cb09
--- /dev/null
+++ b/android/feature/review/src/main/java/com/jaino/review/review_list/ReviewListViewModel.kt
@@ -0,0 +1,58 @@
+package com.jaino.review.review_list
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.jaino.common.flow.EventFlow
+import com.jaino.common.flow.MutableEventFlow
+import com.jaino.common.flow.asEventFlow
+import com.jaino.common.model.UiEvent
+import com.jaino.common.model.UiState
+import com.jaino.data.repository.dictionary.DrinksRepository
+import com.jaino.model.dictionary.Drink
+import model.review.ReviewModel
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+import usecase.review.GetReviewList
+import javax.inject.Inject
+
+@HiltViewModel
+class ReviewListViewModel @Inject constructor(
+ private val getReviewListUseCase: GetReviewList,
+ private val drinksRepository: DrinksRepository
+): ViewModel() {
+
+ private val _reviewEvent = MutableEventFlow>()
+ val reviewEvent : EventFlow> get() = _reviewEvent.asEventFlow()
+
+ private val _reviewItem = MutableStateFlow>>(UiState.Init)
+ val reviewItem : StateFlow>> get() = _reviewItem
+ private val _reviewState = MutableStateFlow(Drink())
+ val reviewState : StateFlow get() = _reviewState
+
+ // ํ์ , ๋๋ค์, ์ฝ๋ฉํธ,
+ fun getReviewList(drinkId : Long){
+ viewModelScope.launch {
+ getReviewListUseCase(drinkId)
+ .onSuccess {
+ _reviewItem.value = UiState.Success(it)
+ }
+ .onFailure {
+ _reviewEvent.emit(UiEvent.Failure(it))
+ }
+ }
+ }
+
+ fun getDrinkInfo(drinkId : Long){
+ viewModelScope.launch {
+ drinksRepository.getDrinkDataById(drinkId)
+ .onSuccess { drinkData ->
+ _reviewState.value = drinkData
+ }
+ .onFailure {
+ _reviewEvent.emit(UiEvent.Failure(it))
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/feature/review/src/main/java/com/jaino/review/review_list/adapter/ReviewAdapter.kt b/android/feature/review/src/main/java/com/jaino/review/review_list/adapter/ReviewAdapter.kt
new file mode 100644
index 00000000..ade718e8
--- /dev/null
+++ b/android/feature/review/src/main/java/com/jaino/review/review_list/adapter/ReviewAdapter.kt
@@ -0,0 +1,40 @@
+package com.jaino.review.review_list.adapter
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import androidx.recyclerview.widget.RecyclerView
+import model.review.ReviewModel
+import com.jaino.review.databinding.ItemReviewBinding
+
+class ReviewAdapter : ListAdapter(callback) {
+
+ companion object{
+ val callback = object : DiffUtil.ItemCallback(){
+ override fun areContentsTheSame(oldItem: ReviewModel, newItem: ReviewModel): Boolean {
+ return oldItem.reviewId == newItem.reviewId
+ }
+
+ override fun areItemsTheSame(oldItem: ReviewModel, newItem: ReviewModel): Boolean {
+ return oldItem == newItem
+ }
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ReviewViewHolder {
+ return ReviewViewHolder(ItemReviewBinding.inflate(
+ LayoutInflater.from(parent.context), parent, false))
+ }
+
+ override fun onBindViewHolder(holder: ReviewViewHolder, position: Int) {
+ holder.bind(currentList[position])
+ }
+
+ inner class ReviewViewHolder(private val binding : ItemReviewBinding)
+ : RecyclerView.ViewHolder(binding.root){
+ fun bind(item : ReviewModel){
+ binding.item = item
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/feature/review/src/main/res/layout/fragment_review_input.xml b/android/feature/review/src/main/res/layout/fragment_review_input.xml
new file mode 100644
index 00000000..c0606c95
--- /dev/null
+++ b/android/feature/review/src/main/res/layout/fragment_review_input.xml
@@ -0,0 +1,234 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/feature/review/src/main/res/layout/fragment_review_list.xml b/android/feature/review/src/main/res/layout/fragment_review_list.xml
new file mode 100644
index 00000000..f5744c40
--- /dev/null
+++ b/android/feature/review/src/main/res/layout/fragment_review_list.xml
@@ -0,0 +1,182 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/feature/review/src/main/res/layout/item_review.xml b/android/feature/review/src/main/res/layout/item_review.xml
new file mode 100644
index 00000000..28d312aa
--- /dev/null
+++ b/android/feature/review/src/main/res/layout/item_review.xml
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/feature/review/src/main/res/navigation/review_nav.xml b/android/feature/review/src/main/res/navigation/review_nav.xml
new file mode 100644
index 00000000..74348c00
--- /dev/null
+++ b/android/feature/review/src/main/res/navigation/review_nav.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/feature/review/src/test/java/com/jaino/review/ExampleUnitTest.kt b/android/feature/review/src/test/java/com/jaino/review/ExampleUnitTest.kt
new file mode 100644
index 00000000..3facbf68
--- /dev/null
+++ b/android/feature/review/src/test/java/com/jaino/review/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.jaino.review
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * 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)
+ }
+}
\ No newline at end of file
diff --git a/android/feature/setting/.gitignore b/android/feature/setting/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/android/feature/setting/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/android/feature/setting/build.gradle.kts b/android/feature/setting/build.gradle.kts
new file mode 100644
index 00000000..8868e843
--- /dev/null
+++ b/android/feature/setting/build.gradle.kts
@@ -0,0 +1,33 @@
+plugins {
+ id("com.jaino.feature")
+ id("com.jaino.hilt")
+ alias(libs.plugins.androidx.navigation.safeargs)
+}
+
+android {
+ namespace = "com.jaino.setting"
+
+ defaultConfig {
+ consumerProguardFiles("consumer-rules.pro")
+ }
+}
+
+dependencies {
+ implementation(project(":core:data"))
+ implementation(project(":core:domain"))
+ implementation(project(":core:model"))
+ implementation(project(":core:common"))
+ implementation(project(":core:designsystem"))
+ implementation(libs.timber)
+ implementation(libs.kotlin.datetime)
+ implementation(libs.androidx.fragment)
+ implementation(libs.bundles.androidx)
+ implementation(libs.androidx.lifecycle.runtime)
+ implementation(libs.androidx.lifecycle.livedata)
+ implementation(libs.bundles.navigation)
+ implementation(libs.material)
+ implementation(libs.processphoenix)
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.androidx.test.junit)
+ androidTestImplementation(libs.androidx.test.espresso)
+}
\ No newline at end of file
diff --git a/android/feature/setting/consumer-rules.pro b/android/feature/setting/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/android/feature/setting/proguard-rules.pro b/android/feature/setting/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/android/feature/setting/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/android/feature/setting/src/androidTest/java/com/jaino/setting/ExampleInstrumentedTest.kt b/android/feature/setting/src/androidTest/java/com/jaino/setting/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..3b3fa336
--- /dev/null
+++ b/android/feature/setting/src/androidTest/java/com/jaino/setting/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.jaino.setting
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.jaino.profile.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/android/feature/setting/src/main/AndroidManifest.xml b/android/feature/setting/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a5918e68
--- /dev/null
+++ b/android/feature/setting/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/android/feature/setting/src/main/java/com/jaino/setting/SettingFragment.kt b/android/feature/setting/src/main/java/com/jaino/setting/SettingFragment.kt
new file mode 100644
index 00000000..88236fd6
--- /dev/null
+++ b/android/feature/setting/src/main/java/com/jaino/setting/SettingFragment.kt
@@ -0,0 +1,143 @@
+package com.jaino.setting
+
+import android.graphics.Color
+import android.os.Bundle
+import android.text.Spannable
+import android.text.SpannableStringBuilder
+import android.text.style.ForegroundColorSpan
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.net.toUri
+import androidx.databinding.DataBindingUtil
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import androidx.lifecycle.flowWithLifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.fragment.findNavController
+import com.jaino.common.model.UiEvent
+import com.jaino.common.navigation.AppNavigator
+import com.jaino.common.widget.ErrorDialog
+import com.jaino.setting.databinding.FragmentSettingBinding
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class SettingFragment : Fragment() {
+
+ @Inject
+ lateinit var appNavigator: AppNavigator
+
+ private var _binding: FragmentSettingBinding? = null
+ private val binding
+ get() = requireNotNull(_binding) { "binding object is not initialized" }
+
+ private val viewModel : SettingViewModel by viewModels()
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ _binding = DataBindingUtil.inflate(inflater, R.layout.fragment_setting, container, false)
+ binding.lifecycleOwner = viewLifecycleOwner
+ binding.viewModel = viewModel
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ initViewModelStates()
+ initButtons()
+ observeData()
+ }
+
+ private fun initViewModelStates(){
+ viewModel.getNickname()
+ }
+
+ private fun initButtons(){
+ binding.settingBackButton.setOnClickListener {
+ navigateToHome()
+ }
+
+ binding.settingAccountCardView.setOnClickListener {
+ navigateToAccount()
+ }
+
+ binding.settingProfileCardView.setOnClickListener {
+ navigateToProfile()
+ }
+
+ binding.historyCardView.setOnClickListener {
+ navigateToHistory()
+ }
+ }
+
+ private fun initNicknameColor(){
+ val text = binding.settingNicknameTitle.text
+ val spannable = SpannableStringBuilder(text)
+ spannable.setSpan(
+ ForegroundColorSpan(Color.BLACK),
+ text.length - 15, text.length,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
+ )
+ binding.settingNicknameTitle.text = spannable
+ }
+
+ private fun observeData(){
+ viewModel.settingUiEvent.flowWithLifecycle(viewLifecycleOwner.lifecycle)
+ .onEach {
+ when(it){
+ is UiEvent.Failure ->{
+ showErrorDialog(it.error)
+ }
+ is UiEvent.Success -> { }
+ }
+ }.launchIn(viewLifecycleOwner.lifecycleScope)
+
+ viewModel.nicknameState.flowWithLifecycle(viewLifecycleOwner.lifecycle)
+ .onEach {
+ if(it.isNotEmpty()){
+ initNicknameColor()
+ }
+ }.launchIn(viewLifecycleOwner.lifecycleScope)
+ }
+
+ private fun showErrorDialog(error: Throwable){
+ ErrorDialog(
+ requireContext(),
+ error = error,
+ onRetryButtonClick = {
+ viewModel.getNickname()
+ }
+ ).show()
+ }
+
+ private fun navigateToHome(){
+ findNavController().navigate("BeJuRyu://feature/home".toUri())
+ }
+
+ private fun navigateToAccount(){
+ startActivity(appNavigator.navigateToAccount())
+ }
+
+ private fun navigateToProfile(){
+ findNavController().navigate(
+ R.id.action_settingFragment_to_profileFragment
+ )
+ }
+
+ private fun navigateToHistory(){
+ findNavController().navigate(
+ R.id.action_settingFragment_to_historyFragment
+ )
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+}
\ No newline at end of file
diff --git a/android/feature/setting/src/main/java/com/jaino/setting/SettingViewModel.kt b/android/feature/setting/src/main/java/com/jaino/setting/SettingViewModel.kt
new file mode 100644
index 00000000..2986ec0d
--- /dev/null
+++ b/android/feature/setting/src/main/java/com/jaino/setting/SettingViewModel.kt
@@ -0,0 +1,38 @@
+package com.jaino.setting
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.jaino.common.flow.EventFlow
+import com.jaino.common.flow.MutableEventFlow
+import com.jaino.common.flow.asEventFlow
+import com.jaino.common.model.UiEvent
+import com.jaino.data.repository.setting.ProfileRepository
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class SettingViewModel @Inject constructor(
+ private val repository: ProfileRepository
+): ViewModel() {
+
+ private val _settingUiEvent : MutableEventFlow> = MutableEventFlow>()
+ val settingUiEvent : EventFlow> get() = _settingUiEvent.asEventFlow()
+
+ private val _nicknameState : MutableStateFlow = MutableStateFlow("")
+ val nicknameState : StateFlow get() = _nicknameState
+
+ fun getNickname(){
+ viewModelScope.launch {
+ repository.getProfile()
+ .onSuccess { profile ->
+ _nicknameState.value = profile.nickname
+ }
+ .onFailure {
+ _settingUiEvent.emit(UiEvent.Failure(it))
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/feature/setting/src/main/java/com/jaino/setting/history/HistoryFragment.kt b/android/feature/setting/src/main/java/com/jaino/setting/history/HistoryFragment.kt
new file mode 100644
index 00000000..37a1e45c
--- /dev/null
+++ b/android/feature/setting/src/main/java/com/jaino/setting/history/HistoryFragment.kt
@@ -0,0 +1,126 @@
+package com.jaino.setting.history
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.net.toUri
+import androidx.databinding.DataBindingUtil
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import androidx.lifecycle.flowWithLifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.fragment.findNavController
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.jaino.common.model.UiEvent
+import com.jaino.common.model.UiState
+import com.jaino.common.widget.ErrorDialog
+import com.jaino.setting.R
+import com.jaino.setting.databinding.FragmentHistoryBinding
+import com.jaino.setting.history.adapter.AnalysisHistoryAdapter
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+@AndroidEntryPoint
+class HistoryFragment: Fragment() {
+
+ private var _binding: FragmentHistoryBinding? = null
+ private val binding
+ get() = requireNotNull(_binding) { "binding object is not initialized" }
+
+ private lateinit var analysisHistoryAdapter : AnalysisHistoryAdapter
+ private val viewModel : HistoryViewModel by viewModels()
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ _binding = DataBindingUtil.inflate(inflater, R.layout.fragment_history, container, false)
+ binding.lifecycleOwner = viewLifecycleOwner
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ initViews()
+ initAdapter()
+ initViewModelStates()
+ observeData()
+ }
+
+ private fun initAdapter(){
+ analysisHistoryAdapter = AnalysisHistoryAdapter(
+ requireContext(),
+ onItemClick = {
+ navigateAnalysisResult(it)
+ }
+ )
+ binding.profileUserAnalyzeList.layoutManager = LinearLayoutManager(requireContext())
+ binding.profileUserAnalyzeList.adapter = analysisHistoryAdapter
+ }
+
+ private fun initViews(){
+ binding.profileBackButton.setOnClickListener {
+ navigateToSetting()
+ }
+ }
+
+ private fun initViewModelStates(){
+ viewModel.getAnalyzeList()
+ }
+
+ private fun observeData(){
+ viewModel.historyUiEvent.flowWithLifecycle(viewLifecycleOwner.lifecycle)
+ .onEach {
+ when(it){
+ is UiEvent.Failure -> {
+ showErrorDialog(it.error)
+ }
+ is UiEvent.Success -> {}
+ }
+ }.launchIn(viewLifecycleOwner.lifecycleScope)
+
+ viewModel.historyListState.flowWithLifecycle(viewLifecycleOwner.lifecycle)
+ .onEach {
+ when(it){
+ is UiState.Init -> {}
+
+ is UiState.Success -> {
+ if(it.data.isNotEmpty()){
+ analysisHistoryAdapter.submitList(it.data)
+ }
+ }
+
+ is UiState.Failure -> {}
+ }
+ }.launchIn(viewLifecycleOwner.lifecycleScope)
+ }
+
+ private fun showErrorDialog(error: Throwable){
+ ErrorDialog(
+ requireContext(),
+ error = error,
+ onRetryButtonClick = {
+ viewModel.getAnalyzeList()
+ }
+ ).show()
+ }
+
+ private fun navigateToSetting(){
+ val direction = HistoryFragmentDirections.actionHistoryFragmentToSettingFragment()
+ findNavController().navigate(direction)
+ }
+
+ private fun navigateAnalysisResult(analysisId: Long){
+ findNavController().navigate(
+ "BeJuRyu://feature/analyze/result?analysisId=$analysisId".toUri()
+ )
+ }
+
+ override fun onDestroy() {
+ _binding = null
+ super.onDestroy()
+ }
+}
\ No newline at end of file
diff --git a/android/feature/setting/src/main/java/com/jaino/setting/history/HistoryViewModel.kt b/android/feature/setting/src/main/java/com/jaino/setting/history/HistoryViewModel.kt
new file mode 100644
index 00000000..a31562f5
--- /dev/null
+++ b/android/feature/setting/src/main/java/com/jaino/setting/history/HistoryViewModel.kt
@@ -0,0 +1,40 @@
+package com.jaino.setting.history
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.jaino.common.flow.EventFlow
+import com.jaino.common.flow.MutableEventFlow
+import com.jaino.common.flow.asEventFlow
+import com.jaino.common.model.UiEvent
+import com.jaino.common.model.UiState
+import com.jaino.data.repository.analysis.AnalysisRepository
+import com.jaino.model.analysis.AnalysisHistory
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class HistoryViewModel @Inject constructor(
+ private val analysisRepository: AnalysisRepository
+ ) : ViewModel(){
+
+ private val _historyUiEvent = MutableEventFlow>()
+ val historyUiEvent : EventFlow> get() = _historyUiEvent.asEventFlow()
+
+ private val _historyListState = MutableStateFlow>>(UiState.Init)
+ val historyListState : StateFlow>> get() = _historyListState
+
+ fun getAnalyzeList(){
+ viewModelScope.launch {
+ analysisRepository.getAnalysisList()
+ .onSuccess {
+ _historyListState.value = UiState.Success(it)
+ }
+ .onFailure {
+ _historyUiEvent.emit(UiEvent.Failure(it))
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/feature/setting/src/main/java/com/jaino/setting/history/adapter/AnalysisHistoryAdapter.kt b/android/feature/setting/src/main/java/com/jaino/setting/history/adapter/AnalysisHistoryAdapter.kt
new file mode 100644
index 00000000..53208b22
--- /dev/null
+++ b/android/feature/setting/src/main/java/com/jaino/setting/history/adapter/AnalysisHistoryAdapter.kt
@@ -0,0 +1,73 @@
+package com.jaino.setting.history.adapter
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.core.content.ContextCompat.getDrawable
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import androidx.recyclerview.widget.RecyclerView
+import com.jaino.common.constant.HAPPY
+import com.jaino.common.constant.MEDIAN
+import com.jaino.common.constant.SAD
+import com.jaino.common.extensions.toSentimentKor
+import com.jaino.model.analysis.AnalysisHistory
+import com.jaino.setting.databinding.ItemUserAnalyzeBinding
+
+class AnalysisHistoryAdapter(
+ private val context : Context,
+ private val onItemClick : (Long) -> Unit
+) : ListAdapter(UserAnalyzeCallback) {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserAnalyzeViewHolder {
+ return UserAnalyzeViewHolder(ItemUserAnalyzeBinding.inflate(
+ LayoutInflater.from(parent.context), parent, false
+ ))
+ }
+
+ override fun onBindViewHolder(holder: UserAnalyzeViewHolder, position: Int) {
+ holder.bind(currentList[position])
+ }
+
+ inner class UserAnalyzeViewHolder(private val binding : ItemUserAnalyzeBinding)
+ : RecyclerView.ViewHolder(binding.root) {
+ fun bind(item : AnalysisHistory) {
+ val sentiment = item.sentiment.filter { it.isLetter() }.toSentimentKor()
+ binding.item = item.copy(sentiment = sentiment)
+ when(item.sentiment){
+ SAD -> {
+ binding.imageView6.setImageDrawable(getDrawable(context, SAD_ICON))
+ }
+ HAPPY -> {
+ binding.imageView6.setImageDrawable(getDrawable(context, HAPPY_ICON))
+ }
+ MEDIAN -> {
+ binding.imageView6.setImageDrawable(getDrawable(context, MEDIAN_ICON))
+ }
+ }
+ binding.root.setOnClickListener {
+ onItemClick(item.id)
+ }
+ }
+ }
+
+ companion object {
+ val UserAnalyzeCallback = object : DiffUtil.ItemCallback(){
+ override fun areItemsTheSame(
+ oldItem: AnalysisHistory, newItem: AnalysisHistory
+ ): Boolean {
+ return oldItem.id == newItem.id
+ }
+
+ override fun areContentsTheSame(
+ oldItem: AnalysisHistory, newItem: AnalysisHistory
+ ): Boolean {
+ return oldItem == newItem
+ }
+ }
+ val SAD_ICON = com.jaino.designsystem.R.drawable.sad
+ val HAPPY_ICON = com.jaino.designsystem.R.drawable.smile
+ val MEDIAN_ICON = com.jaino.designsystem.R.drawable.neutral
+ }
+}
\ No newline at end of file
diff --git a/android/feature/setting/src/main/java/com/jaino/setting/profile/EditNicknameDialog.kt b/android/feature/setting/src/main/java/com/jaino/setting/profile/EditNicknameDialog.kt
new file mode 100644
index 00000000..baf142fe
--- /dev/null
+++ b/android/feature/setting/src/main/java/com/jaino/setting/profile/EditNicknameDialog.kt
@@ -0,0 +1,51 @@
+package com.jaino.setting.profile
+
+import android.app.Dialog
+import android.content.Context
+import android.os.Bundle
+import android.view.WindowManager
+import com.jaino.setting.databinding.DialogEditNicknameBinding
+import usecase.validate.ValidateNickname
+
+class EditNicknameDialog (
+ context : Context,
+ private val onDoneButtonClick: (String) -> Unit
+): Dialog(context){
+
+ private val binding by lazy { DialogEditNicknameBinding.inflate(layoutInflater) }
+ private val validateUseCase by lazy { ValidateNickname() }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ this.setContentView(binding.root)
+ initViews()
+ resizeDialog()
+ }
+
+ private fun initViews(){
+ binding.dialogEditDoneButton.setOnClickListener {
+ if(validateName()){
+ onDoneButtonClick(binding.dialogEditNameTextView.text.toString())
+ dismiss()
+ }
+ }
+ }
+
+ private fun validateName(): Boolean{
+ val name = binding.dialogEditNameTextView.text.toString()
+ val validateResult = validateUseCase(name)
+ if(!validateResult.successful){
+ binding.dialogEditNameLayout.error = validateResult.errorMessage
+ return false
+ }else{
+ return true
+ }
+ }
+
+ private fun resizeDialog(){
+ window?.setLayout(
+ WindowManager.LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.WRAP_CONTENT
+ )
+ }
+}
\ No newline at end of file
diff --git a/android/feature/setting/src/main/java/com/jaino/setting/profile/ProfileFragment.kt b/android/feature/setting/src/main/java/com/jaino/setting/profile/ProfileFragment.kt
new file mode 100644
index 00000000..0bfb7ba0
--- /dev/null
+++ b/android/feature/setting/src/main/java/com/jaino/setting/profile/ProfileFragment.kt
@@ -0,0 +1,101 @@
+package com.jaino.setting.profile
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.databinding.DataBindingUtil
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import androidx.lifecycle.flowWithLifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.fragment.findNavController
+import com.jaino.common.model.UiEvent
+import com.jaino.common.widget.ErrorDialog
+import com.jaino.setting.R
+import com.jaino.setting.databinding.FragmentProfileBinding
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+@AndroidEntryPoint
+class ProfileFragment : Fragment(){
+
+ private var _binding: FragmentProfileBinding? = null
+ private val binding
+ get() = requireNotNull(_binding) { "binding object is not initialized" }
+
+ private val viewModel : ProfileViewModel by viewModels()
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ _binding = DataBindingUtil.inflate(inflater, R.layout.fragment_profile, container, false)
+ binding.lifecycleOwner = viewLifecycleOwner
+ binding.viewModel = viewModel
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ initButtons()
+ initViewModelState()
+ observeData()
+ }
+
+ private fun initButtons() {
+ binding.profileBackButton.setOnClickListener {
+ navigateToSetting()
+ }
+ binding.profileNickNameCardView.setOnClickListener {
+ EditNicknameDialog(
+ requireContext(),
+ onDoneButtonClick = {
+ viewModel.updateNickname(it)
+ }
+ ).show()
+ }
+ }
+
+ private fun initViewModelState(){
+ viewModel.getNickName()
+ }
+
+ private fun observeData(){
+ viewModel.profileUiEvent.flowWithLifecycle(viewLifecycleOwner.lifecycle)
+ .onEach {
+ when(it){
+ is UiEvent.Failure -> {
+ showErrorDialog(it.error)
+ }
+
+ is UiEvent.Success -> {
+
+ }
+ }
+ }.launchIn(viewLifecycleOwner.lifecycleScope)
+ }
+
+ private fun showErrorDialog(error: Throwable){
+ ErrorDialog(
+ requireContext(),
+ error = error,
+ onRetryButtonClick = {
+ viewModel.getNickName()
+ }
+ ).show()
+ }
+
+ private fun navigateToSetting(){
+ findNavController().navigate(
+ R.id.action_profileFragment_to_settingFragment
+ )
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+}
\ No newline at end of file
diff --git a/android/feature/setting/src/main/java/com/jaino/setting/profile/ProfileViewModel.kt b/android/feature/setting/src/main/java/com/jaino/setting/profile/ProfileViewModel.kt
new file mode 100644
index 00000000..a8851325
--- /dev/null
+++ b/android/feature/setting/src/main/java/com/jaino/setting/profile/ProfileViewModel.kt
@@ -0,0 +1,56 @@
+package com.jaino.setting.profile
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.jaino.common.flow.EventFlow
+import com.jaino.common.flow.MutableEventFlow
+import com.jaino.common.flow.asEventFlow
+import com.jaino.common.model.UiEvent
+import com.jaino.data.repository.setting.ProfileRepository
+import com.jaino.data.repository.user.LocalUserRepository
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class ProfileViewModel @Inject constructor(
+ private val localRepository: LocalUserRepository,
+ private val repository: ProfileRepository
+): ViewModel(){
+
+ private val _nickNameState : MutableStateFlow = MutableStateFlow("")
+ val nickNameState : StateFlow get() = _nickNameState
+
+ private val _profileUiEvent : MutableEventFlow> = MutableEventFlow>()
+ val profileUiEvent : EventFlow> get() = _profileUiEvent.asEventFlow()
+
+ fun getNickName(){
+ viewModelScope.launch {
+ repository.getProfile()
+ .onSuccess { profile ->
+ _nickNameState.value = profile.nickname
+ }
+ .onFailure {
+ _profileUiEvent.emit(UiEvent.Failure(it))
+ }
+ }
+ }
+
+ fun updateNickname(nickname: String){
+ viewModelScope.launch {
+ _nickNameState.value = nickname
+ repository.editNickname(localRepository.getUserId(), _nickNameState.value)
+ .onSuccess { profile ->
+ _nickNameState.value = profile.nickname
+ localRepository.setNickname(nickname)
+ }
+ .onFailure {
+ _profileUiEvent.emit(UiEvent.Failure(it))
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/feature/setting/src/main/res/layout/dialog_edit_nickname.xml b/android/feature/setting/src/main/res/layout/dialog_edit_nickname.xml
new file mode 100644
index 00000000..52fa6797
--- /dev/null
+++ b/android/feature/setting/src/main/res/layout/dialog_edit_nickname.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/feature/setting/src/main/res/layout/fragment_history.xml b/android/feature/setting/src/main/res/layout/fragment_history.xml
new file mode 100644
index 00000000..64d67f55
--- /dev/null
+++ b/android/feature/setting/src/main/res/layout/fragment_history.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/feature/setting/src/main/res/layout/fragment_profile.xml b/android/feature/setting/src/main/res/layout/fragment_profile.xml
new file mode 100644
index 00000000..24ebfb0f
--- /dev/null
+++ b/android/feature/setting/src/main/res/layout/fragment_profile.xml
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/feature/setting/src/main/res/layout/fragment_setting.xml b/android/feature/setting/src/main/res/layout/fragment_setting.xml
new file mode 100644
index 00000000..8bf5a3e9
--- /dev/null
+++ b/android/feature/setting/src/main/res/layout/fragment_setting.xml
@@ -0,0 +1,266 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/feature/setting/src/main/res/layout/item_user_analyze.xml b/android/feature/setting/src/main/res/layout/item_user_analyze.xml
new file mode 100644
index 00000000..a327d269
--- /dev/null
+++ b/android/feature/setting/src/main/res/layout/item_user_analyze.xml
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/feature/setting/src/main/res/navigation/setting_nav.xml b/android/feature/setting/src/main/res/navigation/setting_nav.xml
new file mode 100644
index 00000000..2acae108
--- /dev/null
+++ b/android/feature/setting/src/main/res/navigation/setting_nav.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/feature/setting/src/test/java/com/jaino/setting/ExampleUnitTest.kt b/android/feature/setting/src/test/java/com/jaino/setting/ExampleUnitTest.kt
new file mode 100644
index 00000000..bd9aa24d
--- /dev/null
+++ b/android/feature/setting/src/test/java/com/jaino/setting/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.jaino.setting
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * 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)
+ }
+}
\ No newline at end of file
diff --git a/android/gradle.properties b/android/gradle.properties
new file mode 100644
index 00000000..3c5031eb
--- /dev/null
+++ b/android/gradle.properties
@@ -0,0 +1,23 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Kotlin code style for this project: "official" or "obsolete":
+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
diff --git a/android/gradle/libs.version.toml b/android/gradle/libs.version.toml
new file mode 100644
index 00000000..fecb2492
--- /dev/null
+++ b/android/gradle/libs.version.toml
@@ -0,0 +1,158 @@
+[versions]
+compileSdk = "33"
+minSdk = "26"
+targetSdk = "33"
+appVersion = "1.0.0"
+versionCode = "10000"
+desugarJdk = "2.0.3"
+
+gradleplugin = "7.4.2"
+
+kotlin = "1.8.10"
+kotlinx-serialization = "1.5.0"
+kotlinx-serialization-converter = "0.8.0"
+kotlinx-datetime = "0.4.0"
+kotlinx-coroutines = "1.6.4"
+
+androidx-core = "1.9.0"
+androidx-appcompat = "1.6.1"
+androidx-activity = "1.7.0"
+androidx-fragment = "1.5.6"
+androidx-lifecycle = "2.6.1"
+androidx-contstraintlayout = "2.1.4"
+androidx-paging = "3.1.1"
+androidx-room = "2.5.0"
+androidx-security = "1.0.0"
+androidx-splash = "1.0.0"
+androidx-work = "2.8.1"
+androidx-legacy = "1.0.0"
+androidx-hilt-work = "1.0.0"
+androidx-test-junit = "1.1.5"
+androidx-test-espresso = "3.5.1"
+
+androidx-navigation = "2.5.3"
+androidx-camerax = "1.2.1"
+
+google-services-plugin = "4.3.15"
+google-crashlytics-plguin = "2.9.4"
+google-firebase = "31.4.0"
+google-material = "1.8.0"
+square-javapoet = "1.13.0"
+
+retrofit = "2.9.0"
+okhttp = "4.10.0"
+timber = "5.0.1"
+kakao = "2.14.0"
+glide = "4.15.1"
+lottie = "6.0.0"
+processphoenix = "2.1.2"
+progress-view = "1.1.3"
+systemUiController = "1.0.1"
+
+junit = "4.13.2"
+
+hilt = "2.45"
+
+[libraries]
+android-build = { module = "com.android.tools.build:gradle", version.ref = "gradleplugin" }
+kotlin-gradle = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" }
+hilt-gradle = { group = "com.google.dagger", name = "hilt-android-gradle-plugin", version.ref = "hilt" }
+square-javapoet = { group = "com.squareup", name = "javapoet", version.ref = "square-javapoet" }
+nav-safeargs = { module = "androidx.navigation:navigation-safe-args-gradle-plugin", version.ref="androidx-navigation"}
+
+kotlin = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib", version.ref = "kotlin" }
+kotlin-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
+kotlin-coroutines = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
+kotlin-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", version.ref = "kotlinx-datetime" }
+androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidx-core" }
+androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" }
+androidx-lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidx-lifecycle" }
+androidx-security = { module = "androidx.security:security-crypto", version.ref = "androidx-security" }
+androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "androidx-contstraintlayout" }
+androidx-lifecycle-java = { module = "androidx.lifecycle:lifecycle-common-java8", version.ref = "androidx-lifecycle" }
+androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "androidx-lifecycle" }
+androidx-lifecycle-livedata = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "androidx-lifecycle" }
+androidx-paging = { module = "androidx.paging:paging-runtime", version.ref = "androidx-paging" }
+androidx-room = { module = "androidx.room:room-runtime", version.ref = "androidx-room" }
+androidx-room-paging = { module = "androidx.room:room-paging", version.ref = "androidx-room" }
+androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "androidx-room"}
+androidx-work = { module = "androidx.work:work-runtime-ktx", version.ref = "androidx-work" }
+androidx-fragment = { module = "androidx.fragment:fragment-ktx", version.ref = "androidx-fragment" }
+androidx-splash = { module = "androidx.core:core-splashscreen", version.ref = "androidx-splash" }
+androidx-legacy = { module = "androidx.legacy:legacy-support-v4", version.ref = "androidx-legacy" }
+androidx-hilt-work = { module = "androidx.hilt:hilt-work", version.ref = "androidx-hilt-work" }
+androidx-hilt-work-compiler = { module = "androidx.hilt:hilt-compiler", version.ref = "androidx-hilt-work" }
+desugarLibs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugarJdk" }
+
+nav-fragment = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "androidx-navigation"}
+nav-ui = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "androidx-navigation"}
+nav-feature = { module = "androidx.navigation:navigation-dynamic-features-fragment", version.ref="androidx-navigation"}
+camerax-view = { module = "androidx.camera:camera-view", version.ref="androidx-camerax"}
+camerax-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref="androidx-camerax"}
+camerax-extensions = { module = "androidx.camera:camera-extensions", version.ref="androidx-camerax"}
+
+material = { module = "com.google.android.material:material", version.ref = "google-material" }
+
+junit = { module = "junit:junit", version.ref = "junit" }
+androidx-test-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-test-junit" }
+androidx-test-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "androidx-test-espresso" }
+
+okhttp-bom = { module = "com.squareup.okhttp3:okhttp-bom", version.ref = "okhttp" }
+okhttp = { module = "com.squareup.okhttp3:okhttp" }
+okhttp-logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor" }
+crashlytics-plugin = { module = "com.google.firebase:firebase-crashlytics-gradle", version.ref = "google-crashlytics-plguin" }
+firebase = { module = "com.google.firebase:firebase-bom", version.ref = "google-firebase" }
+firebase-analytics = { module = "com.google.firebase:firebase-analytics-ktx" }
+firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics-ktx" }
+retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
+retrofit-kotlin-serialization-converter = { group = "com.jakewharton.retrofit", name = "retrofit2-kotlinx-serialization-converter", version.ref = "kotlinx-serialization-converter" }
+
+timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" }
+
+hilt = { module = "com.google.dagger:hilt-android", version.ref = "hilt" }
+hilt-core = { module = "com.google.dagger:hilt-core", version.ref = "hilt" }
+hilt-kapt = { module = "com.google.dagger:hilt-compiler", version.ref = "hilt" }
+hilt-plugin = { group = "com.google.dagger", name = "hilt-android-gradle-plugin", version.ref = "hilt" }
+
+kakao-login = { module = "com.kakao.sdk:v2-user", version.ref = "kakao" }
+kakao-share = { module = "com.kakao.sdk:v2-share", version.ref = "kakao" }
+processphoenix = { module = "com.jakewharton:process-phoenix", version.ref = "processphoenix" }
+glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide"}
+progressView = { module = "com.github.skydoves:progressview", version.ref = "progress-view"}
+lottie = { module = "com.airbnb.android:lottie", version.ref = "lottie" }
+systemUiController = { module = "land.sungbin:systemuicontroller", version.ref = "systemUiController" }
+
+[bundles]
+
+kotlin = ["kotlin", "kotlin-coroutines", "kotlin-serialization-json", "kotlin-datetime"]
+androidx = [
+ "androidx-appcompat",
+ "androidx-constraintlayout",
+ "androidx-core",
+ "androidx-lifecycle-viewmodel",
+ "androidx-security",
+ "androidx-paging",
+ "androidx-lifecycle-java",
+ "androidx-work",
+ "androidx-legacy",
+ "androidx-fragment",
+ "androidx-lifecycle-runtime",
+ "androidx-lifecycle-livedata"
+]
+room = ["androidx-room", "androidx-room-paging", "androidx-room-compiler"]
+navigation = ["nav_ui", "nav_fragment"]
+camerax = ["camerax-view", "camerax-lifecycle", "camerax-extensions"]
+okhttp = ["okhttp", "okhttp-logging-interceptor"]
+retrofit = ["retrofit", "retrofit-kotlin-serialization-converter"]
+firebase = ["firebase", "firebase", "firebase-crashlytics", "firebase-analytics"]
+
+[plugins]
+android-application = { id = "com.android.application", version.ref = "gradleplugin" }
+android-library = { id = "com.android.library", version.ref = "gradleplugin" }
+androidx-navigation-safeargs= { id = "androidx.navigation.safeargs", version.ref = "androidx-navigation"}
+kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
+kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" }
+google-services = { id = "com.google.gms.google-services", version.ref = "google-services-plugin" }
+google-firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "google-crashlytics-plguin" }
+dagger-hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
+kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..e708b1c0
Binary files /dev/null and b/android/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..2bd1c42d
--- /dev/null
+++ b/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Sat Apr 01 02:49:49 KST 2023
+distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
+distributionPath=wrapper/dists
+zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
diff --git a/android/gradlew b/android/gradlew
new file mode 100755
index 00000000..4f906e0c
--- /dev/null
+++ b/android/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/android/gradlew.bat b/android/gradlew.bat
new file mode 100644
index 00000000..ac1b06f9
--- /dev/null
+++ b/android/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/android/settings.gradle b/android/settings.gradle
new file mode 100644
index 00000000..0f11ef6f
--- /dev/null
+++ b/android/settings.gradle
@@ -0,0 +1,41 @@
+pluginManagement {
+ includeBuild("build-logic")
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ maven { url = uri("https://devrepo.kakao.com/nexus/content/groups/public/") }
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ maven { url 'https://devrepo.kakao.com/nexus/content/groups/public/' }
+ maven { url 'https://jitpack.io' }
+ }
+ versionCatalogs{
+ create("libs"){
+ from(files("gradle/libs.version.toml"))
+ }
+ }
+}
+rootProject.name = "BeJuRyu"
+include ':app'
+include ':feature'
+include ':core'
+include ':core:data'
+include ':core:model'
+include ':core:domain'
+include ':core:network'
+include ':core:datastore'
+include ':core:common'
+include ':core:designsystem'
+include ':feature:home'
+include ':feature:auth'
+include ':feature:setting'
+include ':feature:analysis'
+include ':feature:dictionary'
+include ':feature:review'
+include ':feature:account'
diff --git "a/presentation/[BEJURYU] \341\204\214\341\205\256\341\206\274\341\204\200\341\205\241\341\206\253\341\204\207\341\205\241\341\206\257\341\204\221\341\205\255 -2 (1).pptx" "b/presentation/[BEJURYU] \341\204\214\341\205\256\341\206\274\341\204\200\341\205\241\341\206\253\341\204\207\341\205\241\341\206\257\341\204\221\341\205\255 -2 (1).pptx"
new file mode 100644
index 00000000..b7ea5426
Binary files /dev/null and "b/presentation/[BEJURYU] \341\204\214\341\205\256\341\206\274\341\204\200\341\205\241\341\206\253\341\204\207\341\205\241\341\206\257\341\204\221\341\205\255 -2 (1).pptx" differ