diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index eebe3d7c..a7e57eed 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,6 +28,9 @@ jobs: path: ~/.sonar/cache key: ${{ runner.os }}-sonar restore-keys: ${{ runner.os }}-sonar + - run: echo $LOCAL_PROPERTIES_FILE | base64 -d > local.properties + env: + LOCAL_PROPERTIES_FILE: ${{ secrets.LOCAL_PROPERTIES_FILE }} - name: Build and analyze uses: gradle/gradle-build-action@v2 env: diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 177498e7..26221764 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -22,6 +22,9 @@ jobs: - run: echo $DEBUG_KEYSTORE_FILE | base64 -d > debug.keystore env: DEBUG_KEYSTORE_FILE: ${{ secrets.DEBUG_KEYSTORE_FILE }} + - run: echo $LOCAL_PROPERTIES_FILE | base64 -d > local.properties + env: + LOCAL_PROPERTIES_FILE: ${{ secrets.LOCAL_PROPERTIES_FILE }} - name: Build run: chmod +x ./gradlew && ./gradlew :app::assembleAlpha --quiet env: diff --git a/.gitignore b/.gitignore index 1a1d9d92..8198de2c 100644 --- a/.gitignore +++ b/.gitignore @@ -52,8 +52,8 @@ captures/ # Keystore files # Uncomment the following lines if you do not want to check your keystore files in. -#*.jks -#*.keystore +*.jks +*.keystore # External native build folder generated in Android Studio 2.2 and later .externalNativeBuild diff --git a/app/build.gradle b/app/build.gradle index 4ae5d8dd..2afdc567 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,6 +10,9 @@ plugins { id 'com.google.firebase.crashlytics' } +Properties properties = new Properties() +properties.load(project.rootProject.file('local.properties').newDataInputStream()) + android { compileSdk 33 @@ -24,6 +27,7 @@ android { } signingConfigs { + // GitHub Action 으로 빌드, 배포할 때 사용함 ciDebug { storeFile file("$project.rootDir/debug.keystore") storePassword System.getenv('DEBUG_STORE_PASSWORD') @@ -36,6 +40,19 @@ android { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + manifestPlaceholders = [ + KAKAO_APP_KEY: properties.getProperty('release.kakao.app-key') + ] + buildConfigField 'String', 'BASE_URL', "\"${properties.getProperty('release.three-days.base-url')}\"" + buildConfigField 'String', 'KAKAO_APP_KEY', "\"${properties.getProperty('release.kakao.app-key')}\"" + } + debug { + applicationIdSuffix '.debug' + manifestPlaceholders = [ + KAKAO_APP_KEY: properties.getProperty('debug.kakao.app-key') + ] + buildConfigField 'String', 'BASE_URL', "\"${properties.getProperty('debug.three-days.base-url')}\"" + buildConfigField 'String', 'KAKAO_APP_KEY', "\"${properties.getProperty('debug.kakao.app-key')}\"" } alpha { initWith debug @@ -57,6 +74,7 @@ android { dependencies { implementation(project(":domain")) implementation(project(":data")) + implementation(project(":build-property")) implementation(project(":core")) implementation(project(":navigator")) implementation(project(":presentation:home")) diff --git a/app/src/alpha/google-services.json b/app/src/alpha/google-services.json new file mode 100644 index 00000000..5de693f1 --- /dev/null +++ b/app/src/alpha/google-services.json @@ -0,0 +1,39 @@ +{ + "project_info": { + "project_number": "1091459842156", + "project_id": "three-days-develop", + "storage_bucket": "three-days-develop.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:1091459842156:android:8f8abdfe99c268fe485849", + "android_client_info": { + "package_name": "com.depromeet.threedays.debug" + } + }, + "oauth_client": [ + { + "client_id": "1091459842156-pjhtgjo35d6ro1ra922ngi7aabealvsc.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyB0EiDGdeC8Oxa9HvFshjg0YCZrfhzY4cs" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "1091459842156-pjhtgjo35d6ro1ra922ngi7aabealvsc.apps.googleusercontent.com", + "client_type": 3 + } + ] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/app/src/debug/google-services.json b/app/src/debug/google-services.json new file mode 100644 index 00000000..5de693f1 --- /dev/null +++ b/app/src/debug/google-services.json @@ -0,0 +1,39 @@ +{ + "project_info": { + "project_number": "1091459842156", + "project_id": "three-days-develop", + "storage_bucket": "three-days-develop.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:1091459842156:android:8f8abdfe99c268fe485849", + "android_client_info": { + "package_name": "com.depromeet.threedays.debug" + } + }, + "oauth_client": [ + { + "client_id": "1091459842156-pjhtgjo35d6ro1ra922ngi7aabealvsc.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyB0EiDGdeC8Oxa9HvFshjg0YCZrfhzY4cs" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "1091459842156-pjhtgjo35d6ro1ra922ngi7aabealvsc.apps.googleusercontent.com", + "client_type": 3 + } + ] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/app/google-services.json b/app/src/google-services.json similarity index 100% rename from app/google-services.json rename to app/src/google-services.json diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index dc273ad5..1ff31d66 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -9,7 +9,6 @@ + android:exported="false" + android:noHistory="true" /> + android:theme="@style/WhiteStatusTheme" /> + android:theme="@style/WhiteStatusTheme" /> + android:theme="@style/WhiteStatusTheme" /> @@ -80,6 +83,9 @@ + + @@ -89,8 +95,9 @@ - + diff --git a/app/src/main/java/com/depromeet/threedays/ThreeDaysApplication.kt b/app/src/main/java/com/depromeet/threedays/ThreeDaysApplication.kt index f360f1c5..9063b6f3 100644 --- a/app/src/main/java/com/depromeet/threedays/ThreeDaysApplication.kt +++ b/app/src/main/java/com/depromeet/threedays/ThreeDaysApplication.kt @@ -1,13 +1,19 @@ package com.depromeet.threedays import android.app.Application +import com.depromeet.threedays.buildproperty.BuildProperty +import com.depromeet.threedays.buildproperty.BuildPropertyRepository import com.depromeet.threedays.core.analytics.AnalyticsUtil import com.kakao.sdk.common.KakaoSdk import dagger.hilt.android.HiltAndroidApp import timber.log.Timber +import javax.inject.Inject @HiltAndroidApp class ThreeDaysApplication : Application() { + @Inject + lateinit var buildPropertyRepository: BuildPropertyRepository + override fun onCreate() { super.onCreate() @@ -21,7 +27,10 @@ class ThreeDaysApplication : Application() { } private fun initKakaoSdk() { - KakaoSdk.init(this, "f0c0458b5837b0f245c73b5a22908319") + KakaoSdk.init( + context = this, + appKey = buildPropertyRepository.get(BuildProperty.KAKAO_APP_KEY), + ) } private fun initAnalytics() { diff --git a/app/src/main/java/com/depromeet/threedays/firebase/FCMService.kt b/app/src/main/java/com/depromeet/threedays/firebase/FCMService.kt index 2c2b5f69..63790eda 100644 --- a/app/src/main/java/com/depromeet/threedays/firebase/FCMService.kt +++ b/app/src/main/java/com/depromeet/threedays/firebase/FCMService.kt @@ -13,8 +13,6 @@ import com.depromeet.threedays.domain.usecase.notification.token.UpdateNotificat import com.depromeet.threedays.home.MainActivity import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage -import kotlinx.coroutines.runBlocking -import timber.log.Timber import javax.inject.Inject import com.depromeet.threedays.core_design_system.R as CoreDesignSystemResources @@ -25,18 +23,6 @@ class FCMService @Inject constructor() : FirebaseMessagingService() { override fun onNewToken(token: String) { super.onNewToken(token) Log.i(TAG, "onNewToken: $token") - try { - // FIXME: - // - useCase initialize 안되었다는 에러 발생함. - // - 앱 처음 설치하면 스플래시 화면에서 호출되는데 이 때 memberId 없음 - runBlocking { - updateNotificationTokenUseCase(token) - } - } catch (e: Exception) { - Timber.w( - e, "Failed to update fcm registration token" - ) - } } override fun onMessageReceived(message: RemoteMessage) { diff --git a/app/src/main/java/com/depromeet/threedays/property/BuildPropertyModule.kt b/app/src/main/java/com/depromeet/threedays/property/BuildPropertyModule.kt new file mode 100644 index 00000000..008c8480 --- /dev/null +++ b/app/src/main/java/com/depromeet/threedays/property/BuildPropertyModule.kt @@ -0,0 +1,19 @@ +package com.depromeet.threedays.property + +import com.depromeet.threedays.buildproperty.BuildPropertyRepository +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +class BuildPropertyModule { + + @Singleton + @Provides + fun bindsBuildPropertyRepositoryProvide( + buildPropertyRepositoryImpl: BuildPropertyRepositoryImpl, + ): BuildPropertyRepository = buildPropertyRepositoryImpl +} diff --git a/app/src/main/java/com/depromeet/threedays/property/BuildPropertyRepositoryImpl.kt b/app/src/main/java/com/depromeet/threedays/property/BuildPropertyRepositoryImpl.kt new file mode 100644 index 00000000..677a8e6b --- /dev/null +++ b/app/src/main/java/com/depromeet/threedays/property/BuildPropertyRepositoryImpl.kt @@ -0,0 +1,33 @@ +package com.depromeet.threedays.property + +import com.depromeet.threedays.BuildConfig +import com.depromeet.threedays.buildproperty.BuildProperty +import com.depromeet.threedays.buildproperty.BuildPropertyRepository +import timber.log.Timber +import javax.inject.Inject + +class BuildPropertyRepositoryImpl @Inject constructor() : BuildPropertyRepository { + override fun get(buildProperty: BuildProperty): String { + try { + return readProperties(buildProperty = buildProperty) + } catch (e: Exception) { + throw IllegalStateException( + "Failed to read property from local.properties. key: $buildProperty", + e + ) + } + } + + override fun getOrNull(buildProperty: BuildProperty): String? { + return try { + readProperties(buildProperty = buildProperty) + } catch (e: Exception) { + Timber.e(e, "Failed to read property from local.properties. key: $buildProperty") + null + } + } + + private fun readProperties(buildProperty: BuildProperty): String { + return BuildConfig::class.java.getDeclaredField(buildProperty.key).get(null) as String + } +} diff --git a/build-property/.gitignore b/build-property/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/build-property/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/build-property/build.gradle b/build-property/build.gradle new file mode 100644 index 00000000..184c74a5 --- /dev/null +++ b/build-property/build.gradle @@ -0,0 +1,54 @@ +plugins { + id 'com.android.library' + id 'org.jetbrains.kotlin.android' + id 'kotlin-android' + id 'kotlin-kapt' + id 'kotlin-parcelize' + id 'dagger.hilt.android.plugin' +} + +android { + namespace 'com.depromeet.threedays.buildproperty' + compileSdk 33 + + defaultConfig { + minSdk 26 + targetSdk 33 + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles "consumer-rules.pro" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + alpha { + initWith debug + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + buildFeatures { + dataBinding true + } +} + +dependencies { + implementation(jetpackDeps) + implementation(coroutines) + + implementation deps.hilt.core + kapt deps.hilt.compiler + + implementation deps.timber + + testImplementation(testDeps) + androidTestImplementation(androidTestDeps) +} diff --git a/build-property/consumer-rules.pro b/build-property/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/build-property/proguard-rules.pro b/build-property/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/build-property/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/build-property/src/androidTest/java/com/depromeet/threedays/buildproperty/ExampleInstrumentedTest.kt b/build-property/src/androidTest/java/com/depromeet/threedays/buildproperty/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..10fa7acc --- /dev/null +++ b/build-property/src/androidTest/java/com/depromeet/threedays/buildproperty/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.depromeet.threedays.buildproperty + +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.depromeet.threedays.buildproperty.test", appContext.packageName) + } +} diff --git a/build-property/src/main/AndroidManifest.xml b/build-property/src/main/AndroidManifest.xml new file mode 100644 index 00000000..1a801d81 --- /dev/null +++ b/build-property/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + diff --git a/build-property/src/main/java/com/depromeet/threedays/buildproperty/BuildProperty.kt b/build-property/src/main/java/com/depromeet/threedays/buildproperty/BuildProperty.kt new file mode 100644 index 00000000..de9accea --- /dev/null +++ b/build-property/src/main/java/com/depromeet/threedays/buildproperty/BuildProperty.kt @@ -0,0 +1,21 @@ +package com.depromeet.threedays.buildproperty + +/** + * LocalProperties 에 저장된 값을 buildConfigField 를 통해 접근할 때 사용하는 key 모음. + */ +enum class BuildProperty( + private val description: String, +) { + BASE_URL("api 서버 주소"), + KAKAO_APP_KEY("Kakao native app key"), + ; + + /** + * build.gradle 의 buildConfigField 에 정의한 변수 이름 + */ + val key: String = this.name + + override fun toString(): String { + return "BuildProperty(description='$description', key='$key')" + } +} diff --git a/build-property/src/main/java/com/depromeet/threedays/buildproperty/BuildPropertyRepository.kt b/build-property/src/main/java/com/depromeet/threedays/buildproperty/BuildPropertyRepository.kt new file mode 100644 index 00000000..62f6eca6 --- /dev/null +++ b/build-property/src/main/java/com/depromeet/threedays/buildproperty/BuildPropertyRepository.kt @@ -0,0 +1,6 @@ +package com.depromeet.threedays.buildproperty + +interface BuildPropertyRepository { + fun get(buildProperty: BuildProperty): String + fun getOrNull(buildProperty: BuildProperty): String? +} diff --git a/build-property/src/test/java/com/depromeet/threedays/buildproperty/ExampleUnitTest.kt b/build-property/src/test/java/com/depromeet/threedays/buildproperty/ExampleUnitTest.kt new file mode 100644 index 00000000..92efdbd9 --- /dev/null +++ b/build-property/src/test/java/com/depromeet/threedays/buildproperty/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.depromeet.threedays.buildproperty + +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) + } +} diff --git a/build.gradle b/build.gradle index 4ed88d3e..249b9ad8 100644 --- a/build.gradle +++ b/build.gradle @@ -12,8 +12,8 @@ buildscript { ext { threeDaysApplicationId = "com.depromeet.threedays" - threeDaysAppVersionCode = 6 - threeDaysAppVersionName = "1.0.2" + threeDaysAppVersionCode = 7 + threeDaysAppVersionName = "1.0.3" } } diff --git a/core-design-system/src/main/res/drawable/ic_signup_complete.xml b/core-design-system/src/main/res/drawable/ic_signup_complete.xml new file mode 100644 index 00000000..10a7e9e3 --- /dev/null +++ b/core-design-system/src/main/res/drawable/ic_signup_complete.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + diff --git a/core-design-system/src/main/res/values/demensions.xml b/core-design-system/src/main/res/values/demensions.xml deleted file mode 100644 index 170a267b..00000000 --- a/core-design-system/src/main/res/values/demensions.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - 9dp - 8dp - 7dp - diff --git a/core-design-system/src/main/res/values/themes.xml b/core-design-system/src/main/res/values/themes.xml index bd086f68..03104613 100644 --- a/core-design-system/src/main/res/values/themes.xml +++ b/core-design-system/src/main/res/values/themes.xml @@ -2,12 +2,12 @@ + + diff --git a/core-design-system/src/main/res/values/typography.xml b/core-design-system/src/main/res/values/typography.xml index 3bb6d08e..70963f79 100644 --- a/core-design-system/src/main/res/values/typography.xml +++ b/core-design-system/src/main/res/values/typography.xml @@ -64,19 +64,22 @@ @@ -106,12 +109,14 @@ diff --git a/core/build.gradle b/core/build.gradle index 412c00d5..7c0decd6 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -42,6 +42,7 @@ android { dependencies { implementation(project(":core-design-system")) + implementation(project(":build-property")) implementation(jetpackDeps) implementation(coroutines) diff --git a/core/src/main/java/com/depromeet/threedays/core/util/ThreeDaysImageSnackBar.kt b/core/src/main/java/com/depromeet/threedays/core/util/ThreeDaysImageSnackBar.kt index 886bfbf1..0c6a1e0a 100644 --- a/core/src/main/java/com/depromeet/threedays/core/util/ThreeDaysImageSnackBar.kt +++ b/core/src/main/java/com/depromeet/threedays/core/util/ThreeDaysImageSnackBar.kt @@ -11,6 +11,7 @@ import com.google.android.material.snackbar.Snackbar class ThreeDaysImageSnackBar { fun show( view: View, + imageResId: Int, title: String, content: String, actionText: String, @@ -30,6 +31,7 @@ class ThreeDaysImageSnackBar { params.gravity = Gravity.TOP } + binding.ivIllustrator.setImageResource(imageResId) binding.tvTitle.text = title binding.tvContent.text = content binding.tvActionButton.text = actionText diff --git a/core/src/main/res/layout/fragment_three_days_dialog.xml b/core/src/main/res/layout/fragment_three_days_dialog.xml index b5b8f12b..4bbb51ea 100644 --- a/core/src/main/res/layout/fragment_three_days_dialog.xml +++ b/core/src/main/res/layout/fragment_three_days_dialog.xml @@ -65,7 +65,6 @@ android:layout_marginTop="8dp" android:gravity="center" android:textAppearance="@style/Typography.Paragraph2" - android:lineSpacingExtra="@dimen/Typography.Paragraph2.LineSpacingExtra" android:textColor="@color/gray_500" app:layout_constraintEnd_toEndOf="@+id/tv_title" app:layout_constraintStart_toStartOf="@+id/tv_title" diff --git a/core/src/main/res/layout/snackbar_three_days.xml b/core/src/main/res/layout/snackbar_three_days.xml index f98407a5..1a4e9e9e 100644 --- a/core/src/main/res/layout/snackbar_three_days.xml +++ b/core/src/main/res/layout/snackbar_three_days.xml @@ -30,7 +30,6 @@ android:layout_marginVertical="16dp" android:layout_marginStart="24dp" android:textAppearance="@style/Typography.Paragraph3" - android:lineSpacingExtra="@dimen/Typography.Paragraph3.LineSpacingExtra" android:textColor="@color/white" android:textSize="14sp" app:layout_constraintBottom_toBottomOf="parent" diff --git a/core/src/main/res/layout/toast_three_days.xml b/core/src/main/res/layout/toast_three_days.xml index 0a20f11b..d39d01a8 100644 --- a/core/src/main/res/layout/toast_three_days.xml +++ b/core/src/main/res/layout/toast_three_days.xml @@ -10,6 +10,7 @@ } diff --git a/data/src/main/java/com/depromeet/threedays/data/datasource/auth/AuthRemoteDataSourceImpl.kt b/data/src/main/java/com/depromeet/threedays/data/datasource/auth/AuthRemoteDataSourceImpl.kt index 812eaa76..1f761b8b 100644 --- a/data/src/main/java/com/depromeet/threedays/data/datasource/auth/AuthRemoteDataSourceImpl.kt +++ b/data/src/main/java/com/depromeet/threedays/data/datasource/auth/AuthRemoteDataSourceImpl.kt @@ -3,12 +3,13 @@ package com.depromeet.threedays.data.datasource.auth import com.depromeet.threedays.data.api.AuthService import com.depromeet.threedays.data.entity.auth.PostSignupRequest import com.depromeet.threedays.data.entity.auth.SignupMemberEntity +import com.depromeet.threedays.data.entity.base.ApiResponse import javax.inject.Inject class AuthRemoteDataSourceImpl@Inject constructor( private val authService: AuthService ) : AuthRemoteDataSource { - override suspend fun postSignup(request: PostSignupRequest): SignupMemberEntity { - return authService.postSignup(request = request).data ?: throw IllegalStateException() + override suspend fun postSignup(request: PostSignupRequest): ApiResponse { + return authService.postSignup(request = request) } } diff --git a/data/src/main/java/com/depromeet/threedays/data/di/RepositoryModule.kt b/data/src/main/java/com/depromeet/threedays/data/di/RepositoryModule.kt index d99308ec..353f8664 100644 --- a/data/src/main/java/com/depromeet/threedays/data/di/RepositoryModule.kt +++ b/data/src/main/java/com/depromeet/threedays/data/di/RepositoryModule.kt @@ -23,6 +23,12 @@ abstract class RepositoryModule { repository: OnboardingRepositoryImpl, ): OnboardingRepository + @Binds + @Singleton + abstract fun bindsTodayFirstVisitRepository( + repository: TodayFirstVisitRepositoryImpl, + ): TodayFirstVisitRepository + @Binds @Singleton abstract fun bindsMaxLevelMateRepository( diff --git a/data/src/main/java/com/depromeet/threedays/data/di/ServiceModule.kt b/data/src/main/java/com/depromeet/threedays/data/di/ServiceModule.kt index 7875a151..a71df8f8 100644 --- a/data/src/main/java/com/depromeet/threedays/data/di/ServiceModule.kt +++ b/data/src/main/java/com/depromeet/threedays/data/di/ServiceModule.kt @@ -1,6 +1,8 @@ package com.depromeet.threedays.data.di import android.content.Context +import com.depromeet.threedays.buildproperty.BuildProperty +import com.depromeet.threedays.buildproperty.BuildPropertyRepository import com.depromeet.threedays.data.api.* import com.depromeet.threedays.data.api.deserializer.LocalDateDeserializer import com.depromeet.threedays.data.api.deserializer.LocalDateTimeDeserializer @@ -86,14 +88,23 @@ class NetworkModule { fun providesHttpClient( @ApplicationContext context: Context, gson: Gson, - signupNavigator: SignupNavigator + signupNavigator: SignupNavigator, + buildPropertyRepository: BuildPropertyRepository, ): OkHttpClient { val client = OkHttpClient.Builder() .readTimeout(10, TimeUnit.SECONDS) .connectTimeout(10, TimeUnit.SECONDS) .writeTimeout(10, TimeUnit.SECONDS) val clientWithAuthInterceptor = client - .addInterceptor(AuthInterceptor(context, client.build(), gson, signupNavigator)) + .addInterceptor( + interceptor = AuthInterceptor( + context = context, + client = client.build(), + gson = gson, + signupNavigator = signupNavigator, + buildPropertyRepository = buildPropertyRepository, + ), + ) .addInterceptor(getLoggingInterceptor()) return clientWithAuthInterceptor.build() } @@ -107,7 +118,8 @@ class NetworkModule { @Singleton @Provides fun providesRetrofit( - okHttpClient: OkHttpClient + okHttpClient: OkHttpClient, + buildPropertyRepository: BuildPropertyRepository, ): Retrofit { val gsonWithAdapter: Gson = GsonBuilder() .registerTypeAdapter(LocalDateTime::class.java, LocalDateTimeDeserializer()) @@ -120,7 +132,7 @@ class NetworkModule { .create() return Retrofit.Builder() - .baseUrl(BASE_URL) + .baseUrl(buildPropertyRepository.get(BuildProperty.BASE_URL)) .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create(gsonWithAdapter)) .build() @@ -131,5 +143,3 @@ class NetworkModule { level = HttpLoggingInterceptor.Level.BODY } } - -const val BASE_URL = "https://api.jjaksim.com" diff --git a/data/src/main/java/com/depromeet/threedays/data/entity/RewardHistoryEntity.kt b/data/src/main/java/com/depromeet/threedays/data/entity/RewardHistoryEntity.kt index 1dafd1e0..c0353e34 100644 --- a/data/src/main/java/com/depromeet/threedays/data/entity/RewardHistoryEntity.kt +++ b/data/src/main/java/com/depromeet/threedays/data/entity/RewardHistoryEntity.kt @@ -3,5 +3,5 @@ package com.depromeet.threedays.data.entity import java.time.LocalDateTime data class RewardHistoryEntity( - val createAt: LocalDateTime, + val createAt: LocalDateTime?, ) diff --git a/data/src/main/java/com/depromeet/threedays/data/mapper/MemberMapper.kt b/data/src/main/java/com/depromeet/threedays/data/mapper/MemberMapper.kt index 1e7076ba..c431c16e 100644 --- a/data/src/main/java/com/depromeet/threedays/data/mapper/MemberMapper.kt +++ b/data/src/main/java/com/depromeet/threedays/data/mapper/MemberMapper.kt @@ -1,10 +1,12 @@ package com.depromeet.threedays.data.mapper import com.depromeet.threedays.data.entity.auth.SignupMemberEntity +import com.depromeet.threedays.data.entity.base.ApiResponse import com.depromeet.threedays.data.entity.member.MemberEntity import com.depromeet.threedays.domain.entity.auth.SignupMember import com.depromeet.threedays.domain.entity.member.AuthenticationProvider import com.depromeet.threedays.domain.entity.member.Member +import com.depromeet.threedays.domain.key.RESOURCE_CREATE fun MemberEntity.toMember() = Member( memberId = this.id, @@ -14,11 +16,16 @@ fun MemberEntity.toMember() = Member( resource = this.resource, ) -fun SignupMemberEntity.toSignupMember() = SignupMember( - id = this.id, - name = this.name, - certificationSubject = AuthenticationProvider.from(this.certificationSubject), - token = this.token, - resource = this.resource, - notificationConsent = this.notificationConsent -) +fun ApiResponse.toSignupMember(): SignupMember { + val data = this.data ?: throw IllegalStateException() + val code = this.code + return SignupMember( + id = data.id, + name = data.name, + certificationSubject = AuthenticationProvider.from(data.certificationSubject), + token = data.token, + resource = data.resource, + notificationConsent = data.notificationConsent, + isSignedUp = (code != RESOURCE_CREATE) + ) +} diff --git a/data/src/main/java/com/depromeet/threedays/data/repository/TodayFirstVisitRepositoryImpl.kt b/data/src/main/java/com/depromeet/threedays/data/repository/TodayFirstVisitRepositoryImpl.kt new file mode 100644 index 00000000..3501c514 --- /dev/null +++ b/data/src/main/java/com/depromeet/threedays/data/repository/TodayFirstVisitRepositoryImpl.kt @@ -0,0 +1,16 @@ +package com.depromeet.threedays.data.repository + +import com.depromeet.threedays.data.datasource.datastore.DataStoreDataSource +import com.depromeet.threedays.domain.repository.TodayFirstVisitRepository +import javax.inject.Inject + +class TodayFirstVisitRepositoryImpl @Inject constructor( + private val dataStoreDataSource: DataStoreDataSource, +) : TodayFirstVisitRepository { + + override suspend fun readTodayFirstVisit(key: String): String? = + dataStoreDataSource.readDataStore(key) + + override suspend fun writeTodayFirstVisit(key: String, value: String) = + dataStoreDataSource.writeDataStore(key = key, value = value) +} diff --git a/domain/src/main/java/com/depromeet/threedays/domain/entity/OnboardingType.kt b/domain/src/main/java/com/depromeet/threedays/domain/entity/OnboardingType.kt index c3e9b955..6560b4b4 100644 --- a/domain/src/main/java/com/depromeet/threedays/domain/entity/OnboardingType.kt +++ b/domain/src/main/java/com/depromeet/threedays/domain/entity/OnboardingType.kt @@ -4,4 +4,5 @@ enum class OnboardingType(val key: String) { NOTIFICATION_RECOMMEND("NOTIFICATION_RECOMMEND"), AFTER_SPLASH("AFTER_SPLASH"), MATE("MATE"), + ARCHIVED_HABIT("ARCHIVED_HABIT"), } diff --git a/domain/src/main/java/com/depromeet/threedays/domain/entity/auth/SignupMember.kt b/domain/src/main/java/com/depromeet/threedays/domain/entity/auth/SignupMember.kt index 6341b235..1cd07d7f 100644 --- a/domain/src/main/java/com/depromeet/threedays/domain/entity/auth/SignupMember.kt +++ b/domain/src/main/java/com/depromeet/threedays/domain/entity/auth/SignupMember.kt @@ -8,5 +8,6 @@ data class SignupMember( val name: String, val notificationConsent: Boolean, val resource: String, - val token: Token + val token: Token, + val isSignedUp: Boolean ) diff --git a/domain/src/main/java/com/depromeet/threedays/domain/entity/mate/Mate.kt b/domain/src/main/java/com/depromeet/threedays/domain/entity/mate/Mate.kt index 0fc3ddd9..4824ce91 100644 --- a/domain/src/main/java/com/depromeet/threedays/domain/entity/mate/Mate.kt +++ b/domain/src/main/java/com/depromeet/threedays/domain/entity/mate/Mate.kt @@ -7,7 +7,7 @@ data class Mate( val habitId: Long, val memberId: Long, val title: String, - val createAt: LocalDateTime, + val createAt: LocalDateTime?, // TODO: null로 들어와서 임시 조치 val level: Int, val reward: Int, val rewardHistory: List?, @@ -18,6 +18,6 @@ data class Mate( val status: String, ) { data class RewardHistory( - val createAt: LocalDateTime, + val createAt: LocalDateTime?, ) } diff --git a/domain/src/main/java/com/depromeet/threedays/domain/key/Code.kt b/domain/src/main/java/com/depromeet/threedays/domain/key/Code.kt new file mode 100644 index 00000000..458bafcb --- /dev/null +++ b/domain/src/main/java/com/depromeet/threedays/domain/key/Code.kt @@ -0,0 +1,3 @@ +package com.depromeet.threedays.domain.key + +const val RESOURCE_CREATE = "resource.created" diff --git a/domain/src/main/java/com/depromeet/threedays/domain/repository/TodayFirstVisitRepository.kt b/domain/src/main/java/com/depromeet/threedays/domain/repository/TodayFirstVisitRepository.kt new file mode 100644 index 00000000..958dc828 --- /dev/null +++ b/domain/src/main/java/com/depromeet/threedays/domain/repository/TodayFirstVisitRepository.kt @@ -0,0 +1,6 @@ +package com.depromeet.threedays.domain.repository + +interface TodayFirstVisitRepository { + suspend fun readTodayFirstVisit(key: String): String? + suspend fun writeTodayFirstVisit(key: String, value: String) +} diff --git a/domain/src/main/java/com/depromeet/threedays/domain/usecase/onboarding/ReadOnboardingUseCase.kt b/domain/src/main/java/com/depromeet/threedays/domain/usecase/onboarding/ReadOnboardingUseCase.kt index c9fe8e6b..56230bc9 100644 --- a/domain/src/main/java/com/depromeet/threedays/domain/usecase/onboarding/ReadOnboardingUseCase.kt +++ b/domain/src/main/java/com/depromeet/threedays/domain/usecase/onboarding/ReadOnboardingUseCase.kt @@ -6,12 +6,7 @@ import javax.inject.Inject class ReadOnboardingUseCase @Inject constructor(val repository: OnboardingRepository) { suspend fun execute(onboardingType: OnboardingType): String? { - val key = when (onboardingType) { - OnboardingType.NOTIFICATION_RECOMMEND -> OnboardingType.NOTIFICATION_RECOMMEND.key - OnboardingType.AFTER_SPLASH -> OnboardingType.AFTER_SPLASH.key - OnboardingType.MATE -> OnboardingType.MATE.key - } - return repository.readOnboardnig(key) + return repository.readOnboardnig(onboardingType.key) } } diff --git a/domain/src/main/java/com/depromeet/threedays/domain/usecase/onboarding/WriteOnboardingUseCase.kt b/domain/src/main/java/com/depromeet/threedays/domain/usecase/onboarding/WriteOnboardingUseCase.kt index 751e25d9..d1f28a60 100644 --- a/domain/src/main/java/com/depromeet/threedays/domain/usecase/onboarding/WriteOnboardingUseCase.kt +++ b/domain/src/main/java/com/depromeet/threedays/domain/usecase/onboarding/WriteOnboardingUseCase.kt @@ -6,11 +6,6 @@ import javax.inject.Inject class WriteOnboardingUseCase @Inject constructor(val repository: OnboardingRepository) { suspend fun execute(onboardingType: OnboardingType) { - val key = when (onboardingType) { - OnboardingType.NOTIFICATION_RECOMMEND -> OnboardingType.NOTIFICATION_RECOMMEND.key - OnboardingType.AFTER_SPLASH -> OnboardingType.AFTER_SPLASH.key - OnboardingType.MATE -> OnboardingType.MATE.key - } - return repository.writeOnboardnig(key = key, value = "IS_SHWON") + return repository.writeOnboardnig(key = onboardingType.key, value = "IS_SHWON") } } diff --git a/domain/src/main/java/com/depromeet/threedays/domain/usecase/today_visit/ReadTodayFirstVisitUseCase.kt b/domain/src/main/java/com/depromeet/threedays/domain/usecase/today_visit/ReadTodayFirstVisitUseCase.kt new file mode 100644 index 00000000..373f591c --- /dev/null +++ b/domain/src/main/java/com/depromeet/threedays/domain/usecase/today_visit/ReadTodayFirstVisitUseCase.kt @@ -0,0 +1,14 @@ +package com.depromeet.threedays.domain.usecase.today_visit + +import com.depromeet.threedays.domain.repository.TodayFirstVisitRepository +import javax.inject.Inject + +class ReadTodayFirstVisitUseCase @Inject constructor(val repository: TodayFirstVisitRepository) { + suspend fun execute(): String? { + return repository.readTodayFirstVisit(TODAY_FIRST_VISIT) + } + + companion object { + const val TODAY_FIRST_VISIT = "TODAY_FIRST_VISIT" + } +} diff --git a/domain/src/main/java/com/depromeet/threedays/domain/usecase/today_visit/WriteTodayFirstVisitUseCase.kt b/domain/src/main/java/com/depromeet/threedays/domain/usecase/today_visit/WriteTodayFirstVisitUseCase.kt new file mode 100644 index 00000000..61743a81 --- /dev/null +++ b/domain/src/main/java/com/depromeet/threedays/domain/usecase/today_visit/WriteTodayFirstVisitUseCase.kt @@ -0,0 +1,11 @@ +package com.depromeet.threedays.domain.usecase.today_visit + +import com.depromeet.threedays.domain.repository.TodayFirstVisitRepository +import com.depromeet.threedays.domain.usecase.today_visit.ReadTodayFirstVisitUseCase.Companion.TODAY_FIRST_VISIT +import javax.inject.Inject + +class WriteTodayFirstVisitUseCase @Inject constructor(val repository: TodayFirstVisitRepository) { + suspend fun execute(today: String) { + return repository.writeTodayFirstVisit(key = TODAY_FIRST_VISIT, value = today) + } +} diff --git a/domain/src/main/java/com/depromeet/threedays/domain/util/EmojiUtil.kt b/domain/src/main/java/com/depromeet/threedays/domain/util/EmojiUtil.kt index ef3d64aa..fc06f49c 100644 --- a/domain/src/main/java/com/depromeet/threedays/domain/util/EmojiUtil.kt +++ b/domain/src/main/java/com/depromeet/threedays/domain/util/EmojiUtil.kt @@ -6,6 +6,7 @@ object EmojiUtil{ Word.TRASH to 0x1F5D1, Word.CLOCK to 0x23F0, Word.SMILE to 0x1F600, + Word.QUESTION to 0x2754, ) fun getEmojiString(numberCode: Int?): String { @@ -24,7 +25,8 @@ object EmojiUtil{ FIRE, TRASH, CLOCK, - SMILE + SMILE, + QUESTION } enum class Category { diff --git a/presentation/create/src/main/res/layout/activity_habit_create.xml b/presentation/create/src/main/res/layout/activity_habit_create.xml index a09a0305..9c2372e9 100644 --- a/presentation/create/src/main/res/layout/activity_habit_create.xml +++ b/presentation/create/src/main/res/layout/activity_habit_create.xml @@ -67,10 +67,10 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="12dp" - android:text="@{viewModel.emoji.value}" android:background="@drawable/bg_rect_gray100_r10" - android:padding="10dp" android:gravity="center" + android:padding="10dp" + android:text="@{viewModel.emoji.value}" android:textColor="#FF000000" android:textSize="25sp" app:layout_constraintStart_toStartOf="@+id/tv_habit_name" @@ -305,11 +305,11 @@ android:hint="@string/notification_content_hint" android:importantForAutofill="no" android:inputType="textMultiLine" + android:lineSpacingExtra="5dp" android:maxLength="25" android:paddingHorizontal="16dp" android:paddingVertical="15dp" android:textAppearance="@style/Typography.Paragraph1" - android:lineSpacingExtra="@dimen/Typography.Paragraph1.LineSpacingExtra" android:textColor="@color/gray_800" app:layout_constraintEnd_toEndOf="@+id/tv_notification_time" app:layout_constraintStart_toStartOf="@+id/tv_notification_time" @@ -368,8 +368,7 @@ android:layout_marginHorizontal="5dp" android:layout_weight="1" android:background="@drawable/selector_blue_color_button" - android:button="@null" - /> + android:button="@null" /> (R. ) { binding.tvThisMonthClap.text = rewardCount binding.tvThisMonthAchieveDays.text = achievementCount - binding.tvMostAchieveHabitIcon.text = emoji - binding.tvMostAchieveHabitTitle.text = title binding.clMostAchieve.setBackgroundResource(cardBackgroundResId) + + if(emoji.isEmpty()) { + binding.tvMostAchieveHabitIcon.text = EmojiUtil.getEmojiString(EmojiUtil.Word.QUESTION) + binding.tvMostAchieveHabitTitle.text = getString(R.string.no_achievement_habit_guide) + } else { + binding.tvMostAchieveHabitIcon.text = emoji + binding.tvMostAchieveHabitTitle.text = title + } } private fun setMonth(month: String) { diff --git a/presentation/history/src/main/java/com/depromeet/threedays/history/HistoryViewModel.kt b/presentation/history/src/main/java/com/depromeet/threedays/history/HistoryViewModel.kt index e191afe9..33c1977c 100644 --- a/presentation/history/src/main/java/com/depromeet/threedays/history/HistoryViewModel.kt +++ b/presentation/history/src/main/java/com/depromeet/threedays/history/HistoryViewModel.kt @@ -41,22 +41,24 @@ class HistoryViewModel @Inject constructor( } Status.SUCCESS -> { val habits = response.data!! - val sortedHabits = habits.sortedBy { it.createAt } - val startDate = sortedHabits.first() - val endDate = sortedHabits.last() - - _uiState.update { - it.copy( - habits = habits.map { it.toHabitUI() }, - startDate = getDateTimeFromString(startDate.createAt) ?: it.startDate, - endDate = getDateTimeFromString(endDate.createAt) ?: it.endDate, - ) - } - _uiState.update { - it.copy( - previousMonthClickable = canMoveToPreviousMonth(), - nextMonthClickable = canMoveToNextMonth(), - ) + if(habits.isNotEmpty()) { + val sortedHabits = habits.sortedBy { it.createAt } + val startDate = sortedHabits.first() + val endDate = sortedHabits.last() + + _uiState.update { + it.copy( + habits = habits.map { it.toHabitUI() }, + startDate = getDateTimeFromString(startDate.createAt) ?: it.startDate, + endDate = getDateTimeFromString(endDate.createAt) ?: it.endDate, + ) + } + _uiState.update { + it.copy( + previousMonthClickable = canMoveToPreviousMonth(), + nextMonthClickable = canMoveToNextMonth(), + ) + } } } Status.ERROR -> { diff --git a/presentation/history/src/main/java/com/depromeet/threedays/history/detail/DetailHistoryActivity.kt b/presentation/history/src/main/java/com/depromeet/threedays/history/detail/DetailHistoryActivity.kt index 973e6196..672117a6 100644 --- a/presentation/history/src/main/java/com/depromeet/threedays/history/detail/DetailHistoryActivity.kt +++ b/presentation/history/src/main/java/com/depromeet/threedays/history/detail/DetailHistoryActivity.kt @@ -54,7 +54,7 @@ class DetailHistoryActivity : binding.ivPrevious.setOnSingleClickListener { binding.cvHistory.findFirstVisibleMonth()?.let { calendarMonth -> binding.cvHistory.smoothScrollToMonth(calendarMonth.yearMonth.previousMonth) - onCalendarMonthMoved(calendarMonth.yearMonth.nextMonth) + onCalendarMonthMoved(calendarMonth.yearMonth.previousMonth) } } diff --git a/presentation/history/src/main/java/com/depromeet/threedays/history/detail/DetailHistoryViewModel.kt b/presentation/history/src/main/java/com/depromeet/threedays/history/detail/DetailHistoryViewModel.kt index ffcdb6da..7fd65723 100644 --- a/presentation/history/src/main/java/com/depromeet/threedays/history/detail/DetailHistoryViewModel.kt +++ b/presentation/history/src/main/java/com/depromeet/threedays/history/detail/DetailHistoryViewModel.kt @@ -53,17 +53,19 @@ class DetailHistoryViewModel @Inject constructor( habitId = habitId, from = state.value.fromDate, to = state.value.toDate, - ).map { achievement -> - achievement.achievementDate - } - }.onSuccess { habitAchievementDateList -> - val sortedHabitAchievementDateList = habitAchievementDateList.sorted() + ) + }.onSuccess { achievement -> + val sortedHabitAchievementDateList = achievement.map { it.achievementDate }.sorted() + _state.value = _state.value.copy( isAchievementInitialized = true, achievementDateList = sortedHabitAchievementDateList, - achievementDateWithStatusList = getAchievementDateWithStatusList(sortedHabitAchievementDateList) + achievementDateWithStatusList = getAchievementDateWithStatusList(sortedHabitAchievementDateList), + currentMonthStatic = MonthStatic( + achievements = achievement.size, + claps = achievement.filter { it.sequence == 3 }.size + ) ) - setCurrentMonthStatic() }.onFailure { throwable -> sendErrorMessage(throwable.message) } @@ -125,26 +127,15 @@ class DetailHistoryViewModel @Inject constructor( } } } + /** + * 위의 검증 로직은 추후 업데이트 버전에서 재사용 될 가능성이 있어 삭제하지 않고 유지합니다 + * 다만 연속이 아닌 하나의 동그라미로만 표시돼야 하기 때문에 Status.SINGLE 로 모든 상태를 재할당합니다 */ + map[achievementDateList[index]] = Status.SINGLE } return map } - private fun setCurrentMonthStatic() { - val achievements = state.value.achievementDateWithStatusList.filter { - it.key.monthValue == state.value.currentCalendarDate.monthValue - }.size - val claps = state.value.achievementDateWithStatusList.filter { - it.key.monthValue == state.value.currentCalendarDate.monthValue && it.value == Status.END - }.size - _state.value = _state.value.copy( - currentMonthStatic = MonthStatic( - achievements = achievements, - claps = claps - ) - ) - } - data class State( val isInitialized: Boolean = false, val isAchievementInitialized: Boolean = false, @@ -200,16 +191,13 @@ class DetailHistoryViewModel @Inject constructor( val fromDate: LocalDate get() { val date = currentCalendarDate - return date.minusMonths(1) + return date.withDayOfMonth(1) } val toDate: LocalDate get() { val date = currentCalendarDate - return if (currentCalendarDate.year == today.year && currentCalendarDate.monthValue == today.monthValue) { - date - } else - date.plusMonths(1) + return date.withDayOfMonth(date.lengthOfMonth()) } } diff --git a/presentation/history/src/main/java/com/depromeet/threedays/history/detail/view/Calendar.kt b/presentation/history/src/main/java/com/depromeet/threedays/history/detail/view/Calendar.kt index 6f7c10e5..51c8a0db 100644 --- a/presentation/history/src/main/java/com/depromeet/threedays/history/detail/view/Calendar.kt +++ b/presentation/history/src/main/java/com/depromeet/threedays/history/detail/view/Calendar.kt @@ -45,9 +45,11 @@ class DayBind(private val executeDateWithStatusList: Map = em val rangeBetweenBackground = context.getDrawableCompat(R.drawable.bg_range_middle) as GradientDrawable val singleBackground = context.getDrawableCompat(R.drawable.bg_single_selection) as GradientDrawable val todayBackground = context.getDrawableCompat(R.drawable.bg_today) + val achievedTodayBackground = context.getDrawableCompat(R.drawable.bg_today_selection) as GradientDrawable rangeBetweenBackground.setColor(context.getHabitColor(color)) singleBackground.setColor(context.getHabitColor(color)) + achievedTodayBackground.setColor(context.getHabitColor(color)) container.textView.text = data.date.dayOfMonth.toString() roundBgView.visibility = View.INVISIBLE @@ -90,7 +92,10 @@ class DayBind(private val executeDateWithStatusList: Map = em roundBgView.applyBackground(singleBackground) } Status.SINGLE -> { - roundBgView.applyBackground(singleBackground) + roundBgView.applyBackground( + if(data.date == today) achievedTodayBackground + else singleBackground + ) } } } diff --git a/presentation/history/src/main/res/drawable/bg_today_selection.xml b/presentation/history/src/main/res/drawable/bg_today_selection.xml new file mode 100644 index 00000000..7d47fe69 --- /dev/null +++ b/presentation/history/src/main/res/drawable/bg_today_selection.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/presentation/history/src/main/res/layout/activity_detail_history.xml b/presentation/history/src/main/res/layout/activity_detail_history.xml index 2b802375..3ed26d94 100644 --- a/presentation/history/src/main/res/layout/activity_detail_history.xml +++ b/presentation/history/src/main/res/layout/activity_detail_history.xml @@ -10,357 +10,368 @@ type="com.depromeet.threedays.history.detail.DetailHistoryViewModel" /> - - + + - - - - + android:layout_marginTop="36dp" + android:fillViewport="true" + android:overScrollMode="never" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/iv_back"> - + - - - - + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> - + - + + + - - - + android:layout_marginTop="14dp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/container_title"> - + + + + + app:layout_constraintTop_toBottomOf="@+id/container_date" + tools:text="시작일 : 2022.08.15" /> + app:layout_constraintTop_toBottomOf="@+id/tv_start_date"> + - + app:layout_constraintTop_toBottomOf="@+id/tv_clap"> + + + + + + + + + + android:layout_marginTop="18dp" + android:text="@string/detail_habit_execute_day" + android:textAppearance="@style/Typography.Body3" + android:textColor="@color/gray_500" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + + + + + + + - - + + app:layout_constraintTop_toTopOf="@id/v_calendar_background" + tools:text="202N년 N월" /> - + + + + + + + + + app:layout_constraintTop_toBottomOf="@id/layout_calendar_header" /> + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + diff --git a/presentation/history/src/main/res/layout/fragment_history.xml b/presentation/history/src/main/res/layout/fragment_history.xml index 07125a07..f97f5180 100644 --- a/presentation/history/src/main/res/layout/fragment_history.xml +++ b/presentation/history/src/main/res/layout/fragment_history.xml @@ -1,7 +1,7 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> @@ -47,9 +47,9 @@ android:layout_marginEnd="12dp" android:padding="8dp" android:src="@drawable/ic_left_arrow_default" - app:layout_constraintTop_toTopOf="@+id/tv_this_month" app:layout_constraintBottom_toBottomOf="@+id/tv_this_month" - app:layout_constraintEnd_toStartOf="@+id/tv_this_month"/> + app:layout_constraintEnd_toStartOf="@+id/tv_this_month" + app:layout_constraintTop_toTopOf="@+id/tv_this_month"/> + app:layout_constraintStart_toEndOf="@+id/tv_this_month" + app:layout_constraintTop_toTopOf="@+id/tv_this_month"/> + app:layout_constraintEnd_toEndOf="@id/gl_end" + app:layout_constraintTop_toTopOf="@+id/iv_next_month"/> @@ -110,10 +109,10 @@ android:layout_width="0dp" android:layout_height="0dp" android:fillViewport="true" - app:layout_constraintTop_toBottomOf="@+id/space_bottom_of_toolbar" - app:layout_constraintStart_toStartOf="@id/gl_begin" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="@id/gl_end" - app:layout_constraintBottom_toBottomOf="parent"> + app:layout_constraintStart_toStartOf="@id/gl_begin" + app:layout_constraintTop_toBottomOf="@+id/space_bottom_of_toolbar"> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" /> + app:layout_constraintTop_toTopOf="parent" + tools:text="@string/medicine" /> @@ -299,10 +296,11 @@ android:layout_height="0dp" android:layout_marginTop="12dp" android:clipToPadding="false" + android:overScrollMode="never" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/tv_ongoing_habit" tools:listitem="@layout/item_habit_record"/> @@ -325,13 +323,12 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="24dp" + android:gravity="center" android:text="@string/no_habit_guide" android:textAppearance="@style/Typography.Paragraph3" - android:lineSpacingExtra="@dimen/Typography.Paragraph3.LineSpacingExtra" - android:gravity="center" android:textColor="@color/gray_500" - app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/iv_illustration" /> + app:layout_constraintTop_toBottomOf="@+id/tv_no_habit_guide"/> diff --git a/presentation/history/src/main/res/values/strings.xml b/presentation/history/src/main/res/values/strings.xml index 8326f455..771e4016 100644 --- a/presentation/history/src/main/res/values/strings.xml +++ b/presentation/history/src/main/res/values/strings.xml @@ -13,6 +13,7 @@ 히스토리에 저장되지 않고 삭제된 습관은\n나의 기록에서 확인할 수 없어요. 🥲 아직 습관이 없어요.\n새로운 습관을 만들어볼까요? 습관 만들기 + 아직 실행한\n습관이없어요 👏🏻 diff --git a/presentation/home/src/main/java/com/depromeet/threedays/home/MainActivity.kt b/presentation/home/src/main/java/com/depromeet/threedays/home/MainActivity.kt index 9298f0d9..996ec1e7 100644 --- a/presentation/home/src/main/java/com/depromeet/threedays/home/MainActivity.kt +++ b/presentation/home/src/main/java/com/depromeet/threedays/home/MainActivity.kt @@ -1,10 +1,10 @@ package com.depromeet.threedays.home -import android.animation.Animator import android.os.Bundle import androidx.core.view.isVisible import androidx.fragment.app.Fragment import com.depromeet.threedays.core.BaseActivity +import com.depromeet.threedays.core.util.setOnSingleClickListener import com.depromeet.threedays.history.HistoryFragment import com.depromeet.threedays.home.databinding.ActivityMainBinding import com.depromeet.threedays.home.home.HomeFragment @@ -14,6 +14,8 @@ import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint class MainActivity : BaseActivity(R.layout.activity_main) { + lateinit var onCloseClick: () -> Unit + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -44,31 +46,26 @@ class MainActivity : BaseActivity(R.layout.activity_main) { } } - private fun changeFragment(fragment: Fragment) { - supportFragmentManager.beginTransaction().replace(binding.flMain.id, fragment).commit() - } - private fun initEvent() { - binding.lottieClap.addAnimatorListener(object : Animator.AnimatorListener { - override fun onAnimationStart(p0: Animator) { - binding.congratulationAnimationGroup.isVisible = true - } - - override fun onAnimationEnd(p0: Animator) { - binding.congratulationAnimationGroup.isVisible = false - } - - override fun onAnimationCancel(p0: Animator) { + binding.ivClose.setOnSingleClickListener { + stopCongratulateThirdClapAnimation() + onCloseClick() - } - - override fun onAnimationRepeat(p0: Animator) { + } + } - } - }) + fun changeFragment(fragment: Fragment) { + supportFragmentManager.beginTransaction().replace(binding.flMain.id, fragment).commit() } - fun startCongratulateThirdClapAnimation() { + fun startCongratulateThirdClapAnimation(onCloseClick: () -> Unit) { + binding.congratulationAnimationGroup.isVisible = true binding.lottieClap.playAnimation() + this.onCloseClick = onCloseClick + } + + fun stopCongratulateThirdClapAnimation() { + binding.congratulationAnimationGroup.isVisible = false + binding.lottieClap.cancelAnimation() } } diff --git a/presentation/home/src/main/java/com/depromeet/threedays/home/home/HabitAdapter.kt b/presentation/home/src/main/java/com/depromeet/threedays/home/home/HabitAdapter.kt index 0ce9394a..18fcdba8 100644 --- a/presentation/home/src/main/java/com/depromeet/threedays/home/home/HabitAdapter.kt +++ b/presentation/home/src/main/java/com/depromeet/threedays/home/home/HabitAdapter.kt @@ -8,7 +8,7 @@ import com.depromeet.threedays.home.home.model.HabitUI import kotlin.reflect.KFunction0 class HabitAdapter( - private val createHabitAchievement: (Long, Boolean) -> Unit, + private val createHabitAchievement: (HabitUI) -> Unit, private val deleteHabitAchievement: (Long, Long) -> Unit, private val onCreateHabitClick: KFunction0, private val onMoreClick: (HabitUI) -> Unit, diff --git a/presentation/home/src/main/java/com/depromeet/threedays/home/home/HabitViewHolder.kt b/presentation/home/src/main/java/com/depromeet/threedays/home/home/HabitViewHolder.kt index 371d9278..3835bffd 100644 --- a/presentation/home/src/main/java/com/depromeet/threedays/home/home/HabitViewHolder.kt +++ b/presentation/home/src/main/java/com/depromeet/threedays/home/home/HabitViewHolder.kt @@ -17,7 +17,7 @@ class HabitViewHolder(private val view: ItemHabitBinding) : RecyclerView.ViewHol fun onBind( habitUI: HabitUI, context: Context, - createHabitAchievement: (Long, Boolean) -> Unit, + createHabitAchievement: (HabitUI) -> Unit, deleteHabitAchievement: (Long, Long) -> Unit, onMoreClick: (HabitUI) -> Unit ) { @@ -31,32 +31,41 @@ class HabitViewHolder(private val view: ItemHabitBinding) : RecyclerView.ViewHol habitUI.run { view.tvHabitDayOfWeek.text = convertDayListToString(dayOfWeeks) - for (targetIndex in 0..2) { - if (targetIndex < todayIndex) { + if(todayIndex == 0 && todayHabitAchievementId != null) { + for (i in 0..2) { setCheckedButton( - targetIndex = targetIndex, + targetIndex = i, resId = checkedBackgroundResId ) - } else if (targetIndex == todayIndex) { - if(isTodayChecked) { - setUncheckedButton( + } + } else { + for (targetIndex in 0..2) { + if (targetIndex < todayIndex) { + setCheckedButton( targetIndex = targetIndex, - resId = R.drawable.bg_oval_gray, - textColor = R.color.gray_400 + resId = checkedBackgroundResId ) + } else if (targetIndex == todayIndex) { + if(isTodayChecked) { + setUncheckedButton( + targetIndex = targetIndex, + resId = R.drawable.bg_oval_gray, + textColor = R.color.gray_400 + ) + } else { + setUncheckedButton( + targetIndex = targetIndex, + resId = checkableBackgroundResId, + textColor = checkableTextColor + ) + } } else { setUncheckedButton( targetIndex = targetIndex, - resId = checkableBackgroundResId, - textColor = checkableTextColor + resId = R.drawable.bg_oval_gray, + textColor = R.color.gray_400 ) } - } else { - setUncheckedButton( - targetIndex = targetIndex, - resId = R.drawable.bg_oval_gray, - textColor = R.color.gray_400 - ) } } } @@ -129,7 +138,7 @@ class HabitViewHolder(private val view: ItemHabitBinding) : RecyclerView.ViewHol private fun initEvent( habitUI: HabitUI, - createHabitAchievement: (Long, Boolean) -> Unit, + createHabitAchievement: (HabitUI) -> Unit, deleteHabitAchievement: (Long, Long) -> Unit, onMoreClick: (HabitUI) -> Unit ) { @@ -164,10 +173,12 @@ class HabitViewHolder(private val view: ItemHabitBinding) : RecyclerView.ViewHol private fun switchHabitState( clickedIndex: Int, habitUI: HabitUI, - createHabitAchievement: (Long, Boolean) -> Unit, + createHabitAchievement: (HabitUI) -> Unit, deleteHabitAchievement: (Long, Long) -> Unit, ) { - val isDeleteAchievementAvailable = habitUI.isTodayChecked && (clickedIndex == habitUI.todayIndex - 1) + val isDeleteAchievementAvailable = + (habitUI.isTodayChecked && (clickedIndex == habitUI.todayIndex - 1)) + || (habitUI.isTodayChecked && habitUI.todayIndex == 0) val isCreateAchievementAvailable = habitUI.isTodayChecked.not() && (clickedIndex == habitUI.todayIndex) if (isDeleteAchievementAvailable) { @@ -181,7 +192,7 @@ class HabitViewHolder(private val view: ItemHabitBinding) : RecyclerView.ViewHol textColor = habitUI.checkableTextColor ) } else if(isCreateAchievementAvailable) { - createHabitAchievement(habitUI.habitId, clickedIndex == LAST_CLAP_INDEX) + createHabitAchievement(habitUI) setCheckedButton( targetIndex = clickedIndex, resId = habitUI.checkedBackgroundResId @@ -200,7 +211,5 @@ class HabitViewHolder(private val view: ItemHabitBinding) : RecyclerView.ViewHol ) ) } - - const val LAST_CLAP_INDEX = 2 } } diff --git a/presentation/home/src/main/java/com/depromeet/threedays/home/home/HomeFragment.kt b/presentation/home/src/main/java/com/depromeet/threedays/home/home/HomeFragment.kt index 2a52f306..98a6626c 100644 --- a/presentation/home/src/main/java/com/depromeet/threedays/home/home/HomeFragment.kt +++ b/presentation/home/src/main/java/com/depromeet/threedays/home/home/HomeFragment.kt @@ -29,10 +29,8 @@ import com.depromeet.threedays.home.databinding.FragmentHomeBinding import com.depromeet.threedays.home.home.dialog.MoreActionModal import com.depromeet.threedays.home.home.dialog.NotiGuideBottomSheet import com.depromeet.threedays.home.home.dialog.NotiRecommendBottomSheet -import com.depromeet.threedays.navigator.ArchivedHabitNavigator -import com.depromeet.threedays.navigator.HabitCreateNavigator -import com.depromeet.threedays.navigator.HabitUpdateNavigator -import com.depromeet.threedays.navigator.NotificationNavigator +import com.depromeet.threedays.mate.MateFragment +import com.depromeet.threedays.navigator.* import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch import java.time.ZoneId @@ -56,38 +54,29 @@ class HomeFragment : BaseFragment(R.layout.f @Inject lateinit var archivedHabitNavigator: ArchivedHabitNavigator - private val addResultLauncher = - registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + private val addResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> - when (result.resultCode) { - RESULT_CREATE -> viewModel.fetchGoals() - RESULT_UPDATE -> { - viewModel.fetchGoals() - ThreeDaysToast().show( - requireContext(), - resources.getString(com.depromeet.threedays.core.R.string.toast_habit_modify_complete) - ) - } + when(result.resultCode) { + RESULT_CREATE -> viewModel.fetchGoals() + RESULT_UPDATE -> { + viewModel.fetchGoals() + ThreeDaysToast().show( + requireContext(), + resources.getString(com.depromeet.threedays.core.R.string.toast_habit_modify_complete) + ) } } + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - viewModel.checkIsFirstVisitor() - checkNotificationPermission() initAdapter() setObserve() initView() initEvent() } - private fun checkNotificationPermission() { - val isDeviceNotificationOn = - NotificationManagerCompat.from(requireContext()).areNotificationsEnabled() - viewModel.setDeviceNotificationState(isDeviceNotificationOn) - } - private fun onCreateHabitClick() { if(viewModel.habits.value.isEmpty()) { AnalyticsUtil.event( @@ -115,9 +104,7 @@ class HomeFragment : BaseFragment(R.layout.f private fun initAdapter() { habitAdapter = HabitAdapter( - createHabitAchievement = { habitId, isThirdClap -> - viewModel.createHabitAchievement(habitId, isThirdClap) - }, + createHabitAchievement = { viewModel.createHabitAchievement(it) }, deleteHabitAchievement = { habitId, habitAchievementId -> viewModel.deleteHabitAchievement( habitId = habitId, @@ -215,21 +202,22 @@ class HomeFragment : BaseFragment(R.layout.f ) } - - private fun showBottomSheet( - isDeviceNotificationOn: Boolean, - isFirstVisitor: Boolean, + private fun showImageSnackBar( + view: View, + imageResId: Int, + titleResId: Int, + contentResId: Int, + mateLevel: Int, + onAction: () -> Unit ) { - if(!isDeviceNotificationOn) { - NotiGuideBottomSheet.newInstance ( - moveToSettingForTurnOnPermission = { moveToSettingForTurnOnPermission() } - ).show(parentFragmentManager, NotiGuideBottomSheet.TAG) - } - if(isFirstVisitor) { - val modal = NotiRecommendBottomSheet() - modal.setStyle(DialogFragment.STYLE_NORMAL, core_design.style.RoundCornerBottomSheetDialogTheme) - modal.show(parentFragmentManager, NotiGuideBottomSheet.TAG) - } + ThreeDaysImageSnackBar().show( + view = view, + imageResId = imageResId, + title = getString(titleResId), + content = getString(contentResId, mateLevel), + actionText = getString(R.string.move), + onAction = onAction + ) } private fun moveToSettingForTurnOnPermission() { @@ -238,6 +226,23 @@ class HomeFragment : BaseFragment(R.layout.f startActivity(intent) } + private fun showNotiRecommendBottomSheet() { + val modal = NotiRecommendBottomSheet( + onConfirmClick = { checkNotificationPermission() } + ) + modal.setStyle(DialogFragment.STYLE_NORMAL, core_design.style.RoundCornerBottomSheetDialogTheme) + modal.show(parentFragmentManager, NotiGuideBottomSheet.TAG) + } + + private fun checkNotificationPermission() { + val isDeviceNotificationOn = NotificationManagerCompat.from(requireContext()).areNotificationsEnabled() + if(!isDeviceNotificationOn) { + NotiGuideBottomSheet.newInstance ( + moveToSettingForTurnOnPermission = { moveToSettingForTurnOnPermission() } + ).show(parentFragmentManager, NotiGuideBottomSheet.TAG) + } + } + private fun setObserve() { lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { @@ -266,14 +271,6 @@ class HomeFragment : BaseFragment(R.layout.f binding.rvGoal.visibility = if (list.isEmpty()) View.GONE else View.VISIBLE } } - launch { - viewModel.uiState.collect { - showBottomSheet( - isDeviceNotificationOn = it.isDeviceNotificationOn, - isFirstVisitor = it.isFirstVisitor, - ) - } - } launch { viewModel.uiEffect.collect { when (it) { @@ -307,7 +304,21 @@ class HomeFragment : BaseFragment(R.layout.f ) } ) - UiEffect.ShowClapAnimation -> (requireActivity() as MainActivity).startCongratulateThirdClapAnimation() + is UiEffect.ShowImageSnackBar -> showImageSnackBar( + view = binding.clTopLayout, + imageResId = it.imageResId, + titleResId = it.titleResId, + contentResId = it.contentResId, + mateLevel = it.mateLevel, + onAction = { + (requireActivity() as MainActivity).changeFragment(MateFragment()) + } + ) + is UiEffect.ShowClapAnimation -> { + (requireActivity() as MainActivity).startCongratulateThirdClapAnimation { viewModel.checkLevelUpHabit(it.habitId) } + } + UiEffect.ShowNotiRecommendBottomSheet -> showNotiRecommendBottomSheet() + UiEffect.ShowNotiGuideBottomSheet -> checkNotificationPermission() } } } diff --git a/presentation/home/src/main/java/com/depromeet/threedays/home/home/HomeViewModel.kt b/presentation/home/src/main/java/com/depromeet/threedays/home/home/HomeViewModel.kt index 42e58672..3c838721 100644 --- a/presentation/home/src/main/java/com/depromeet/threedays/home/home/HomeViewModel.kt +++ b/presentation/home/src/main/java/com/depromeet/threedays/home/home/HomeViewModel.kt @@ -13,9 +13,11 @@ import com.depromeet.threedays.domain.usecase.onboarding.WriteOnboardingUseCase import com.depromeet.threedays.home.R import com.depromeet.threedays.home.home.model.HabitUI import com.depromeet.threedays.home.home.model.toHabitUI +import com.depromeet.threedays.mate.MateImageResourceResolver.Companion.levelToResourceFunction import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch +import java.time.LocalDate import javax.inject.Inject @HiltViewModel @@ -32,42 +34,29 @@ class HomeViewModel @Inject constructor( val habits: StateFlow> get() = _habits - private val _uiState: MutableStateFlow = MutableStateFlow(UiState()) - val uiState: StateFlow - get() = _uiState - private val _uiEffect: MutableSharedFlow = MutableSharedFlow() val uiEffect: SharedFlow get() = _uiEffect init { fetchGoals() + checkIsFirstVisitor() } - fun checkIsFirstVisitor() { + private fun checkIsFirstVisitor() { viewModelScope.launch { - if (uiState.value.isFirstVisitor.not()) { - val response = readOnboardingUseCase.execute(OnboardingType.NOTIFICATION_RECOMMEND) - _uiState.update { - it.copy( - isFirstVisitor = (response == null) - ) - } - writeOnboardingUseCase.execute(OnboardingType.NOTIFICATION_RECOMMEND) - _uiState.update { - it.copy( - isFirstVisitor = false - ) - } + val response = readOnboardingUseCase.execute(OnboardingType.NOTIFICATION_RECOMMEND) + if(response == null) { + _uiEffect.emit( + UiEffect.ShowNotiRecommendBottomSheet + ) + } else { + _uiEffect.emit( + UiEffect.ShowNotiGuideBottomSheet + ) } - } - } - fun setDeviceNotificationState(isDeviceNotificationOn: Boolean) { - _uiState.update { - it.copy( - isDeviceNotificationOn = isDeviceNotificationOn, - ) + writeOnboardingUseCase.execute(OnboardingType.NOTIFICATION_RECOMMEND) } } @@ -92,15 +81,16 @@ class HomeViewModel @Inject constructor( } } - fun createHabitAchievement(habitId: Long, isThirdClap: Boolean) { + fun createHabitAchievement(habitUI: HabitUI) { viewModelScope.launch { - createHabitAchievementUseCase(habitId).collect { response -> + createHabitAchievementUseCase(habitUI.habitId).collect { response -> when(response.status) { Status.LOADING -> { } Status.SUCCESS -> { fetchGoals() + checkNewClap(habitUI) } Status.ERROR -> { @@ -110,12 +100,6 @@ class HomeViewModel @Inject constructor( } } } - - if(isThirdClap) { - _uiEffect.emit( - UiEffect.ShowClapAnimation - ) - } } } @@ -247,12 +231,40 @@ class HomeViewModel @Inject constructor( } } } -} -data class UiState ( - val isDeviceNotificationOn: Boolean = true, - val isFirstVisitor: Boolean = false, -) + private fun checkNewClap(habitUI: HabitUI) { + val hasNewClap = habitUI.todayIndex == 2 && habitUI.sequence == 2 && !habitUI.isTodayChecked + if(hasNewClap) { + viewModelScope.launch { + _uiEffect.emit( + UiEffect.ShowClapAnimation(habitUI.habitId) + ) + } + } + } + + fun checkLevelUpHabit(habitId: Long) { + val habitWithMate = habits.value.find { it.mate != null && it.todayIndex == 0 && it.sequence > 0 && it.isTodayChecked } ?: return + if(habitWithMate.habitId != habitId) return + val mate = habitWithMate.mate ?: return + val levelUpAt = mate.levelUpAt ?: return + val today = LocalDate.now().toString() + + if(today == levelUpAt.toLocalDate().toString()) { + viewModelScope.launch { + _uiEffect.emit( + UiEffect.ShowImageSnackBar( + imageResId = levelToResourceFunction(mate.level), + titleResId = R.string.level_up_title, + contentResId = R.string.level_up_content, + actionTextResId = R.string.move, + mateLevel = mate.level, + ) + ) + } + } + } +} sealed interface UiEffect { data class ShowToastMessage(val resId: Int): UiEffect @@ -269,7 +281,18 @@ sealed interface UiEffect { val textResId: Int, val actionTextResId: Int, ): UiEffect - object ShowClapAnimation: UiEffect + data class ShowImageSnackBar( + val imageResId: Int, + val titleResId: Int, + val contentResId: Int, + val actionTextResId: Int, + val mateLevel: Int, + ) : UiEffect + data class ShowClapAnimation( + val habitId: Long, + ) : UiEffect + object ShowNotiGuideBottomSheet : UiEffect + object ShowNotiRecommendBottomSheet : UiEffect } sealed interface HabitType { diff --git a/presentation/home/src/main/java/com/depromeet/threedays/home/home/dialog/NotiRecommendBottomSheet.kt b/presentation/home/src/main/java/com/depromeet/threedays/home/home/dialog/NotiRecommendBottomSheet.kt index 507a4368..b3e04e81 100644 --- a/presentation/home/src/main/java/com/depromeet/threedays/home/home/dialog/NotiRecommendBottomSheet.kt +++ b/presentation/home/src/main/java/com/depromeet/threedays/home/home/dialog/NotiRecommendBottomSheet.kt @@ -9,7 +9,9 @@ import com.depromeet.threedays.core.util.setOnSingleClickListener import com.depromeet.threedays.home.R import com.google.android.material.bottomsheet.BottomSheetDialogFragment -class NotiRecommendBottomSheet : BottomSheetDialogFragment() { +class NotiRecommendBottomSheet( + val onConfirmClick: () -> Unit +) : BottomSheetDialogFragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -25,6 +27,7 @@ class NotiRecommendBottomSheet : BottomSheetDialogFragment() { val btnConfirm = view.findViewById