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 + + + + + + + \ 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