Skip to content

[FEAT] 터치 미션을 구현합니다. #200

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Mar 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
ff615b9
[ADD/#195] 이미지 리소스 추가
MoonsuKang Mar 19, 2025
9d2cdcf
[ADD/#195] 로띠 파일 추가
MoonsuKang Mar 19, 2025
b6b64f4
[REFACTOR/#195] Lottie 전환 개선 및 재생 제어 기능 추가
MoonsuKang Mar 19, 2025
9344f77
[ADD/#195] 미션타입 상태 추가
MoonsuKang Mar 19, 2025
0153db0
[ADD/#195] 랜덤 미션 타입 추가 및 클릭 핸들러 함수 구현
MoonsuKang Mar 19, 2025
2e2f5ca
[REFACTOR/#195] 미션 타입에 따른 라벨 및 이미지 변경 로직 추가
MoonsuKang Mar 19, 2025
4a61168
[ADD/#195] 미션 타입에 따른 진행 상태 및 애니메이션 처리 로직 추가
MoonsuKang Mar 19, 2025
8e797e7
[ADD/#195] firebase-config 의존성 추가
MoonsuKang Mar 26, 2025
7bf1449
[ADD/#195] core:remoteconfig 모듈 생성
MoonsuKang Mar 26, 2025
9fdef71
[ADD/#195] core:remoteconfig 의존성 주입
MoonsuKang Mar 26, 2025
480ec62
[FEAT/#195] firebase-remoteconfig 모듈 및 매니저 클래스 구현
MoonsuKang Mar 26, 2025
efb5b66
[FEAT/#195] RemoteConfigRepository 인터페이스 정의
MoonsuKang Mar 26, 2025
32cbccb
[ADD/#195] RemoteConfig 응답 값을 기반으로 미션 타입 매핑 로직 추가
MoonsuKang Mar 26, 2025
d686e64
[FEAT/#195] RemoteConfig 미션 타입 조회 UseCase 구현
MoonsuKang Mar 26, 2025
e391e83
[FEAT/#195] RemoteConfigRepositoryImpl 구현(Remote-Config값 가져오는 역할)
MoonsuKang Mar 26, 2025
ad07578
[ADD/#195] RemoteConfigRepositoryImpl DI 설정
MoonsuKang Mar 26, 2025
12e5b5b
[FEAT/#195] MissionViewModel에서 원격 미션 타입 로드 기능 추가
MoonsuKang Mar 26, 2025
8d41b4b
REFAC: MissionContract에서 MissionType을 도메인 모델로 변경
MoonsuKang Mar 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions core/designsystem/src/main/res/raw/mission_letter_tap.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions core/remoteconfig/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
15 changes: 15 additions & 0 deletions core/remoteconfig/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import com.yapp.convention.setNamespace

plugins {
id("orbit.android.library")
id("orbit.android.hilt")
}

android {
setNamespace("core.remoteconfig")
}

dependencies {
implementation(platform(libs.firebase.bom))
implementation(libs.firebase.config)
}
Empty file.
21 changes: 21 additions & 0 deletions core/remoteconfig/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions core/remoteconfig/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.yapp.remoteconfig

import com.google.firebase.remoteconfig.FirebaseRemoteConfig
import kotlinx.coroutines.tasks.await
import javax.inject.Inject

class FirebaseRemoteConfigManager @Inject constructor(
private val remoteConfig: FirebaseRemoteConfig,
) {
suspend fun fetchAndActivate(): Boolean {
return try {
remoteConfig.fetchAndActivate().await()
} catch (e: Exception) {
false
}
}

fun getRawMissionType(): String {
val rawValue = remoteConfig.getString(KEY_MISSION_TYPE)
return rawValue
}

companion object {
private const val KEY_MISSION_TYPE = "alarm_mission_type"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.yapp.remoteconfig.di

import com.google.firebase.ktx.Firebase
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
import com.google.firebase.remoteconfig.ktx.remoteConfig
import com.google.firebase.remoteconfig.ktx.remoteConfigSettings
import com.yapp.remoteconfig.FirebaseRemoteConfigManager
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object RemoteConfigModule {

@Provides
@Singleton
fun provideFirebaseRemoteConfig(): FirebaseRemoteConfig {
return Firebase.remoteConfig.apply {
setConfigSettingsAsync(
remoteConfigSettings {
minimumFetchIntervalInSeconds = 3600L
},
)
}
}

@Provides
@Singleton
fun provideRemoteConfigManager(
remoteConfig: FirebaseRemoteConfig,
): FirebaseRemoteConfigManager = FirebaseRemoteConfigManager(remoteConfig)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
Expand All @@ -26,12 +27,17 @@ fun LottieAnimation(
contentScale: ContentScale = ContentScale.FillWidth,
scaleXAdjustment: Float = 1f,
scaleYAdjustment: Float = 1f,
play: Boolean = iterations == 1,
restartOnPlay: Boolean = false,
onAnimationEnd: (() -> Unit)? = null,
) {
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(resId))
val progress by animateLottieCompositionAsState(
val isPlaying = remember { mutableStateOf(iterations == LottieConstants.IterateForever || play) }
val animationState = animateLottieCompositionAsState(
composition = composition,
iterations = iterations,
isPlaying = isPlaying.value,
restartOnPlay = restartOnPlay,
)
val alpha = remember { Animatable(0f) }

Expand All @@ -44,9 +50,18 @@ fun LottieAnimation(
}
}

LaunchedEffect(progress) {
if (progress == 1f) {
LaunchedEffect(play) {
if (play) {
isPlaying.value = true
}
}

LaunchedEffect(animationState.progress) {
if (animationState.progress == 1f) {
onAnimationEnd?.invoke()
if (iterations == 1) {
isPlaying.value = false
}
}
}

Expand All @@ -62,7 +77,7 @@ fun LottieAnimation(
if (composition != null) {
com.airbnb.lottie.compose.LottieAnimation(
composition = composition,
progress = { progress },
progress = { animationState.progress },
modifier = Modifier.fillMaxSize(),
)
}
Expand Down
1 change: 1 addition & 0 deletions data/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ dependencies {
implementation(projects.core.network)
implementation(projects.core.datastore)
implementation(projects.core.media)
implementation(projects.core.remoteconfig)

ksp(libs.androidx.room.compiler)
implementation(libs.androidx.room.ktx)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package com.yapp.data.remote.di

import com.yapp.data.remote.repositoryimpl.DummyRepositoryImpl
import com.yapp.data.remote.repositoryimpl.FortuneRepositoryImpl
import com.yapp.data.remote.repositoryimpl.RemoteConfigRepositoryImpl
import com.yapp.data.remote.repositoryimpl.SignUpRepositoryImpl
import com.yapp.data.remote.repositoryimpl.UserInfoRepositoryImpl
import com.yapp.domain.repository.DummyRepository
import com.yapp.domain.repository.FortuneRepository
import com.yapp.domain.repository.RemoteConfigRepository
import com.yapp.domain.repository.SignUpRepository
import com.yapp.domain.repository.UserInfoRepository
import dagger.Binds
Expand Down Expand Up @@ -40,4 +42,10 @@ abstract class RepositoryModule {
abstract fun bindsFortuneRepository(
fortuneRepository: FortuneRepositoryImpl,
): FortuneRepository

@Binds
@Singleton
abstract fun bindsRemoteConfigRepository(
remoteConfigRepository: RemoteConfigRepositoryImpl,
): RemoteConfigRepository
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.yapp.data.remote.repositoryimpl

import com.yapp.domain.model.MissionType
import com.yapp.domain.repository.RemoteConfigRepository
import com.yapp.remoteconfig.FirebaseRemoteConfigManager
import javax.inject.Inject

class RemoteConfigRepositoryImpl @Inject constructor(
private val manager: FirebaseRemoteConfigManager,
) : RemoteConfigRepository {

override suspend fun fetchAndActivate(): Boolean = manager.fetchAndActivate()

override fun getMissionType(): MissionType {
return MissionType.fromRemoteValue(manager.getRawMissionType())
}
}
18 changes: 18 additions & 0 deletions domain/src/main/java/com/yapp/domain/model/MissionType.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.yapp.domain.model

sealed class MissionType {
data object Shake : MissionType()
data object Click : MissionType()

companion object {
fun fromRemoteValue(value: String): MissionType {
return when (value) {
"tap_mission" -> Click
"shake_mission" -> Shake
else -> {
Click
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.yapp.domain.repository

import com.yapp.domain.model.MissionType

interface RemoteConfigRepository {
suspend fun fetchAndActivate(): Boolean
fun getMissionType(): MissionType
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.yapp.domain.usecase

import com.yapp.domain.model.MissionType
import com.yapp.domain.repository.RemoteConfigRepository
import javax.inject.Inject

class GetMissionTypeUseCase @Inject constructor(
private val repository: RemoteConfigRepository,
) {
suspend fun execute(): MissionType {
repository.fetchAndActivate()
return repository.getMissionType()
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
package com.yapp.mission

import com.yapp.domain.model.MissionType

sealed class MissionContract {

data class State(
val missionType: MissionType = MissionType.Click,
val showOverlayText: Boolean = false,
val showOverlay: Boolean = true,
val missionProgress: Int = 0,
val isMissionCompleted: Boolean = false,
val isFlipped: Boolean = false,
val shakeCount: Int = 0,
val clickCount: Int = 0,
val playWhenClick: Boolean = false,
val showFinalAnimation: Boolean = false,
val isFlipped: Boolean = false,
val rotationY: Float = 0f,
val rotationZ: Float = 0f,
val showExitDialog: Boolean = false,
Expand Down
Loading