Skip to content
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

[ Feature ] : 버전 업데이트 다이얼로그 구현 #303

Merged
merged 17 commits into from
Jul 28, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
c0a9ce9
feat: VersionEntity 생성
hoyahozz Jul 22, 2023
03991a5
feat: Remote Config 버전 정보 조회 로직 구현
hoyahozz Jul 22, 2023
2c4a208
fix : 디버그 빌드일 때 1분마다 Remote Config 를 업데이트하도록 구현
hoyahozz Jul 22, 2023
b35c6d4
fix : VersionEntity 의 필드 어노테이션을 SerialName 으로 변경
hoyahozz Jul 22, 2023
53723bc
refactor : VersionType 네이밍 수정
hoyahozz Jul 22, 2023
5c122d9
fix : Version 코드를 Long 형태로 저장하도록 수정
hoyahozz Jul 22, 2023
e890bf8
feat: 플레이스토어 이동 함수 구현
hoyahozz Jul 22, 2023
77420d2
feat: ResourceProvider 에서 현재 버전 코드를 가져올 수 있도록 구현
hoyahozz Jul 22, 2023
e88342c
feat: YDSPopupDialog 의 'Negative' 버튼을 Nullable 하게 수정
hoyahozz Jul 22, 2023
8392d39
feat: 로그인 화면 버전 검증 로직 구현
hoyahozz Jul 22, 2023
eefdecb
feat: 오늘의 세션 화면 버전 검증 로직 구현
hoyahozz Jul 22, 2023
2a2c1c4
feat: 앱 실행 후 업데이트 요청을 단 한 번만 진행하도록 구현
hoyahozz Jul 22, 2023
263b5af
fix: Repository 캐싱을 통해 버전 업데이트 요청 여부를 저장하도록 구현
hoyahozz Jul 22, 2023
bcf81be
refactor: 확장 함수 및 스코프 함수를 이용해 코드 리팩터링 @EvergreenTree97
hoyahozz Jul 27, 2023
59d7496
fix: 컨플릭트 해결
hoyahozz Jul 27, 2023
69d04a3
feat : 버전 정보 조회 실패 시 안내 다이얼로그 출력 구현
hoyahozz Jul 28, 2023
1a4c01d
Merge branch 'develop' into feature/version-update-dialog
hoyahozz Jul 28, 2023
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
47 changes: 27 additions & 20 deletions common/src/main/java/com/yapp/common/yds/YDSPopupDialog.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ fun YDSPopupDialog(
title: String,
content: String,
modifier: Modifier = Modifier,
negativeButtonText: String,
negativeButtonText: String? = null,
positiveButtonText: String,
onClickNegativeButton: () -> Unit,
onClickNegativeButton: (() -> Unit)? = null,
onClickPositiveButton: () -> Unit,
editTextInitValue: String = "",
editTextHint: String? = null,
Expand Down Expand Up @@ -127,31 +127,38 @@ fun YDSPopupDialog(
Row(
modifier = Modifier.padding(top = 18.dp)
) {
Button(
onClick = onClickNegativeButton,
modifier = Modifier
.weight(1f)
.height(46.dp)
.padding(end = 6.dp),
colors = ButtonDefaults.buttonColors(
backgroundColor = AttendanceTheme.colors.grayScale.Gray200
),
shape = RoundedCornerShape(10.dp),
elevation = null
) {
Text(
text = negativeButtonText,
style = AttendanceTypography.body1,
color = AttendanceTheme.colors.grayScale.Gray800
)
if (onClickNegativeButton != null && negativeButtonText != null) {
Button(
onClick = onClickNegativeButton,
modifier = Modifier
.weight(1f)
.height(46.dp)
.padding(end = 6.dp),
colors = ButtonDefaults.buttonColors(
backgroundColor = AttendanceTheme.colors.grayScale.Gray200
),
shape = RoundedCornerShape(10.dp),
elevation = null
) {
Text(
text = negativeButtonText,
style = AttendanceTypography.body1,
color = AttendanceTheme.colors.grayScale.Gray800
)
}
Comment on lines +130 to +148
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

YDSPopupDialog에 들어가는 버튼영역(Row)를 함수의 composable로 받도록 하고 버튼 콜백을 제거한 뒤
YDSPopUpDialog를 이용해서 YDSPopUpOneButtonDialog / YDSPopUpTwoButtonDialog 이렇게 만들면

YDSPopUpDialog는 불필요하게 매번 두가지의 콜백, 버튼Text를 받을 필요가 없고
OneButton / TwoButton 컴포저블들외에 다른 타입을 추가할때 제약이 없을것 같아요!

물론 지금 바로 수정하기엔 시간이 부족한지라 이후에 개선해보면 좋을것 같습니다!
이번에 운영진 화면을 개선하면서 이렇게 하는게 좋은 방법인것 같아 공유해봅니다.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵ㅎㅎ 이슈로 등록해놓고, 추후에 개선하면 좋을 것 같습니다! 좋은 의견 감사드려요 :)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

동의합니다~~ 추후 슬롯 API 의 개념을 참고해서 하면 좋을 듯 합니다!

}

val positiveButtonPadding =
if (onClickNegativeButton != null && negativeButtonText != null)
Modifier.padding(horizontal = 12.dp)
else Modifier.padding(start = 6.dp)

Button(
onClick = onClickPositiveButton,
modifier = Modifier
.weight(1f)
.height(46.dp)
.padding(start = 6.dp),
.then(positiveButtonPadding),
colors = ButtonDefaults.buttonColors(
backgroundColor = AttendanceTheme.colors.etcColors.EtcRed // EtcRed or YappOrange
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.yapp.data.datasource
import com.yapp.data.model.ConfigEntity
import com.yapp.data.model.SessionEntity
import com.yapp.data.model.TeamEntity
import com.yapp.data.model.VersionEntity


interface FirebaseRemoteConfigDataSource {
Expand All @@ -12,4 +13,5 @@ interface FirebaseRemoteConfigDataSource {
suspend fun getTeamList(): List<TeamEntity>
suspend fun getQrPassword(): String
suspend fun shouldShowGuestButton(): Boolean
}
suspend fun getVersionInfo(): VersionEntity
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ package com.yapp.data.datasource
import com.google.firebase.ktx.Firebase
import com.google.firebase.remoteconfig.ktx.remoteConfig
import com.google.firebase.remoteconfig.ktx.remoteConfigSettings
import com.yapp.data.BuildConfig
import com.yapp.data.model.ConfigEntity
import com.yapp.data.model.SessionEntity
import com.yapp.data.model.TeamEntity
import com.yapp.data.model.VersionEntity
import com.yapp.domain.firebase.RemoteConfigData
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.suspendCancellableCoroutine
Expand All @@ -19,7 +21,9 @@ import kotlin.coroutines.resumeWithException
class FirebaseRemoteConfigDataSourceImpl @Inject constructor() : FirebaseRemoteConfigDataSource {

private val firebaseRemoteConfig = Firebase.remoteConfig
private val configSettings = remoteConfigSettings { minimumFetchIntervalInSeconds = 3600 }
private val configSettings = remoteConfigSettings {
minimumFetchIntervalInSeconds = if (BuildConfig.DEBUG) 60 else 3600
}

init {
firebaseRemoteConfig.setDefaultsAsync(RemoteConfigData.defaultMaps)
Expand Down Expand Up @@ -107,4 +111,18 @@ class FirebaseRemoteConfigDataSourceImpl @Inject constructor() : FirebaseRemoteC
}
}

}
override suspend fun getVersionInfo(): VersionEntity {
return suspendCancellableCoroutine { cancellableContinuation ->
firebaseRemoteConfig.fetchAndActivate().addOnSuccessListener {
val version = firebaseRemoteConfig.getString(RemoteConfigData.VersionInfo.key)
.let { jsonString ->
Json.decodeFromString<VersionEntity>(jsonString)
}

cancellableContinuation.resume(version, null)
}.addOnFailureListener { exception ->
cancellableContinuation.resumeWithException(exception)
}
}
}
}
24 changes: 24 additions & 0 deletions data/src/main/java/com/yapp/data/model/VersionEntity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.yapp.data.model

import com.yapp.domain.model.Version
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class VersionEntity(
@SerialName("min_version_code")
val minVersionCode: Long? = null,
@SerialName("max_version_code")
val maxVersionCode: Long? = null,
@SerialName("current_version")
val currentVersion: String? = null,
)

fun VersionEntity.toDomain(isAlreadyRequestVersionUpdate: Boolean): Version {
return Version(
isAlreadyRequestVersionUpdate = isAlreadyRequestVersionUpdate,
minVersionCode = minVersionCode!!,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!! 를 사용하는 것 보다, minversionCode ?: throw IllegalArgumentException 같이 명시적인 예외를 던지는 편이 좋을 것 같습니다!
직렬화 문제 등으로 인해 필드 누락이 발생할 시 예외를 개발자가 구분할 수 있지 않을 까 싶습니당

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분은 현재는 시간이 없으므로.. 추후에 일괄 수정하는 것을 함께 검토해보면 좋을 것 같습니다!

maxVersionCode = maxVersionCode!!,
currentVersion = currentVersion!!
)
}
Comment on lines +8 to +24
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VersionEntity가 전부 nullable 처리 되어있는데 어떤 이슈 때문일까요 ?
강제로 언래핑시키면 하나라도 내려오지 않을 경우 익셉션이 발생할거 같아서요 !

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분은 기존 코드를 많이 참고했습니다!

아마 Repository 에서 toDomain 을 할 때, 받아온 데이터가 null 이면 모두 실패 했다라는 결과물을 출력하길 원하셨던 것 같아요!

Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import com.yapp.data.datasource.FirebaseRemoteConfigDataSource
import com.yapp.data.model.ConfigEntity
import com.yapp.data.model.SessionEntity
import com.yapp.data.model.TeamEntity
import com.yapp.data.model.VersionEntity
import com.yapp.data.model.toDomain
import com.yapp.domain.model.Config
import com.yapp.domain.model.Session
import com.yapp.domain.model.Team
import com.yapp.domain.model.Version
import com.yapp.domain.repository.RemoteConfigRepository
import javax.inject.Inject

Expand All @@ -16,6 +18,8 @@ class RemoteConfigRepositoryImpl @Inject constructor(
private val firebaseRemoteConfigDataSource: FirebaseRemoteConfigDataSource,
) : RemoteConfigRepository {

private var isAlreadyRequestUpdate = false
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Repository가 싱글톤으로 생성되다 보니, 매번 앱 진입 시 한번만 업데이트 체크를 해주겠군요!
알아갑니다👍


override suspend fun getMaginotlineTime(): Result<String> {
return runCatching {
firebaseRemoteConfigDataSource.getMaginotlineTime()
Expand Down Expand Up @@ -94,4 +98,18 @@ class RemoteConfigRepositoryImpl @Inject constructor(
)
}

override suspend fun getVersionInfo(): Result<Version> {
return runCatching {
firebaseRemoteConfigDataSource.getVersionInfo()
}.fold(
onSuccess = { entity: VersionEntity ->
Result.success(entity.toDomain(isAlreadyRequestUpdate)).also {
isAlreadyRequestUpdate = true
}
},
onFailure = { exception ->
Result.failure(exception)
}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,28 @@ sealed class RemoteConfigData<T> {
override val defaultValue: String = "fail"
}

object VersionInfo : RemoteConfigData<String>() {
override val key: String = ATTENDANCE_VERSION_INFO
override val defaultValue: String = ""
}

companion object {
private const val ATTENDANCE_MAGINOTLINE_TIME = "attendance_maginotline_time"
private const val ATTENDANCE_SESSION_LIST = "attendance_session_list"
private const val ATTENDANCE_SELECT_TEAMS = "attendance_select_teams"
private const val ATTENDANCE_CONFIG = "config"
private const val ATTENDANCE_QR_PASSWORD = "attendance_qr_password"
private const val SHOULD_SHOW_GUEST_BUTTON = "should_show_guest_button"
private const val ATTENDANCE_VERSION_INFO = "attendance_version"

val defaultMaps = mapOf(
MaginotlineTime.defaultValue to MaginotlineTime.key,
SessionList.defaultValue to SessionList.key,
AttendanceSelectTeams.defaultValue to AttendanceSelectTeams.key,
Config.defaultValue to Config.key,
QrPassword.defaultValue to QrPassword.key,
ShouldShowGuestButton.defaultValue to ShouldShowGuestButton.key
ShouldShowGuestButton.defaultValue to ShouldShowGuestButton.key,
VersionInfo.defaultValue to VersionInfo.key
)
}

}
8 changes: 8 additions & 0 deletions domain/src/main/java/com/yapp/domain/model/Version.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.yapp.domain.model

data class Version(
val isAlreadyRequestVersionUpdate: Boolean,
val minVersionCode: Long,
val maxVersionCode: Long,
val currentVersion: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.yapp.domain.model.types

enum class VersionType {
NOT_REQUIRED,
REQUIRED,
UPDATED_BUT_NOT_REQUIRED
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.yapp.domain.repository
import com.yapp.domain.model.Config
import com.yapp.domain.model.Session
import com.yapp.domain.model.Team
import com.yapp.domain.model.Version


interface RemoteConfigRepository {
Expand All @@ -12,4 +13,5 @@ interface RemoteConfigRepository {
suspend fun getTeamList(): Result<List<Team>>
suspend fun getQrPassword(): Result<String>
suspend fun shouldShowGuestButton(): Result<Boolean>
}
suspend fun getVersionInfo(): Result<Version>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.yapp.domain.usecases

import com.yapp.domain.model.types.VersionType
import com.yapp.domain.repository.RemoteConfigRepository
import javax.inject.Inject

class CheckVersionUpdateUseCase @Inject constructor(
private val remoteConfigRepository: RemoteConfigRepository,
) {

suspend operator fun invoke(currentVersionCode: Long): Result<VersionType> {
return remoteConfigRepository.getVersionInfo().mapCatching { version ->
val minVersionCode = version.minVersionCode
val maxVersionCode = version.maxVersionCode

return@mapCatching when {
currentVersionCode < minVersionCode -> VersionType.REQUIRED
currentVersionCode == maxVersionCode -> VersionType.NOT_REQUIRED
else -> {
if (version.isAlreadyRequestVersionUpdate) VersionType.NOT_REQUIRED
else VersionType.UPDATED_BUT_NOT_REQUIRED
}
}
Comment on lines +7 to +23
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

하나의 UseCase 단위로 깔끔하게 떨어지니 보기도 좋고
재사용하기도 좋네요!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

평소 유즈케이스를 repository function의 래핑 정도로만 인지하고 있었는데, usecase를 기능 단위로 제공할 수 있다는 점이 인상 깊네요!

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import com.google.accompanist.insets.systemBarsPadding
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.yapp.common.theme.AttendanceTheme
import com.yapp.common.yds.YDSToast
import com.yapp.presentation.ui.MainContract.MainUiEvent
import com.yapp.presentation.ui.MainContract.MainUiSideEffect
import com.yapp.presentation.ui.admin.AdminConstants.KEY_LAST_SESSION_ID
import com.yapp.presentation.ui.admin.AdminConstants.KEY_SESSION_ID
import com.yapp.presentation.ui.admin.AdminConstants.KEY_SESSION_TITLE
Expand Down Expand Up @@ -56,11 +58,11 @@ fun AttendanceScreen(
LaunchedEffect(key1 = viewModel.effect) {
viewModel.effect.collect { effect ->
when (effect) {
is MainContract.MainUiSideEffect.NavigateToQRScreen -> {
is MainUiSideEffect.NavigateToQRScreen -> {
navController.navigate(BottomNavigationItem.QR_AUTH.route)
}

is MainContract.MainUiSideEffect.ShowToast -> {
is MainUiSideEffect.ShowToast -> {
qrToastVisible = !qrToastVisible
delay(1000L)
qrToastVisible = !qrToastVisible
Expand Down Expand Up @@ -149,7 +151,7 @@ fun AttendanceScreen(
MemberMain(
navigateToScreen = { route ->
if (route == AttendanceScreenRoute.QR_AUTH.route) {
viewModel.setEvent(MainContract.MainUiEvent.OnClickQrAuthButton)
viewModel.setEvent(MainUiEvent.OnClickQrAuthButton)
} else {
navController.navigate(route)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.yapp.common.base.BaseViewModel
import com.yapp.domain.usecases.CheckQrAuthTimeUseCase
import com.yapp.presentation.R
import com.yapp.presentation.common.AttendanceBundle
import com.yapp.presentation.ui.MainContract.MainUiEvent
import com.yapp.presentation.util.ResourceProvider
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.coroutineScope
Expand All @@ -13,13 +14,13 @@ import javax.inject.Inject
class MainViewModel @Inject constructor(
private val resourcesProvider: ResourceProvider,
private val checkQrAuthTime: CheckQrAuthTimeUseCase
) : BaseViewModel<MainContract.MainUiState, MainContract.MainUiSideEffect, MainContract.MainUiEvent>(
) : BaseViewModel<MainContract.MainUiState, MainContract.MainUiSideEffect, MainUiEvent>(
MainContract.MainUiState()
) {

override suspend fun handleEvent(event: MainContract.MainUiEvent) {
override suspend fun handleEvent(event: MainUiEvent) {
when (event) {
MainContract.MainUiEvent.OnClickQrAuthButton -> checkAttendanceValidate()
MainUiEvent.OnClickQrAuthButton -> checkAttendanceValidate()
}
}

Expand Down
Loading