Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import com.threegap.bitnagil.data.routine.datasource.RoutineRemoteDataSource
import com.threegap.bitnagil.data.routine.datasourceImpl.RoutineRemoteDataSourceImpl
import com.threegap.bitnagil.data.user.datasource.UserDataSource
import com.threegap.bitnagil.data.user.datasourceImpl.UserDataSourceImpl
import com.threegap.bitnagil.data.version.datasource.VersionDataSource
import com.threegap.bitnagil.data.version.datasourceImpl.VersionDataSourceImpl
import com.threegap.bitnagil.data.writeroutine.datasource.WriteRoutineDataSource
import com.threegap.bitnagil.data.writeroutine.datasourceImpl.WriteRoutineDataSourceImpl
import dagger.Binds
Expand Down Expand Up @@ -57,4 +59,8 @@ abstract class DataSourceModule {
@Binds
@Singleton
abstract fun bindRecommendRoutineDataSource(recommendRoutineDataSourceImpl: RecommendRoutineDataSourceImpl): RecommendRoutineDataSource

@Binds
@Singleton
abstract fun bindVersionDataSource(versionDataSourceImpl: VersionDataSourceImpl): VersionDataSource
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import com.threegap.bitnagil.data.onboarding.repositoryImpl.OnBoardingRepository
import com.threegap.bitnagil.data.recommendroutine.repositoryImpl.RecommendRoutineRepositoryImpl
import com.threegap.bitnagil.data.routine.repositoryImpl.RoutineRepositoryImpl
import com.threegap.bitnagil.data.user.repositoryImpl.UserRepositoryImpl
import com.threegap.bitnagil.data.version.repositoryImpl.VersionRepositoryImpl
import com.threegap.bitnagil.data.writeroutine.repositoryImpl.WriteRoutineRepositoryImpl
import com.threegap.bitnagil.domain.auth.repository.AuthRepository
import com.threegap.bitnagil.domain.emotion.repository.EmotionRepository
import com.threegap.bitnagil.domain.onboarding.repository.OnBoardingRepository
import com.threegap.bitnagil.domain.recommendroutine.repository.RecommendRoutineRepository
import com.threegap.bitnagil.domain.routine.repository.RoutineRepository
import com.threegap.bitnagil.domain.user.repository.UserRepository
import com.threegap.bitnagil.domain.version.repository.VersionRepository
import com.threegap.bitnagil.domain.writeroutine.repository.WriteRoutineRepository
import dagger.Binds
import dagger.Module
Expand Down Expand Up @@ -51,4 +53,8 @@ abstract class RepositoryModule {
@Binds
@Singleton
abstract fun bindRecommendRoutineRepository(recommendRoutineRepositoryImpl: RecommendRoutineRepositoryImpl): RecommendRoutineRepository

@Binds
@Singleton
abstract fun bindVersionRepository(versionRepositoryImpl: VersionRepositoryImpl): VersionRepository
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.threegap.bitnagil.data.onboarding.service.OnBoardingService
import com.threegap.bitnagil.data.recommendroutine.service.RecommendRoutineService
import com.threegap.bitnagil.data.routine.service.RoutineService
import com.threegap.bitnagil.data.user.service.UserService
import com.threegap.bitnagil.data.version.service.VersionService
import com.threegap.bitnagil.data.writeroutine.service.WriteRoutineService
import com.threegap.bitnagil.di.core.Auth
import com.threegap.bitnagil.di.core.NoneAuth
Expand Down Expand Up @@ -60,4 +61,9 @@ object ServiceModule {
@Singleton
fun provideRecommendRoutineService(@Auth retrofit: Retrofit): RecommendRoutineService =
retrofit.create(RecommendRoutineService::class.java)

@Provides
@Singleton
fun provideVersionService(@NoneAuth retrofit: Retrofit): VersionService =
retrofit.create(VersionService::class.java)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.threegap.bitnagil.convention

import com.android.build.api.dsl.ApplicationExtension
import com.threegap.bitnagil.convention.extension.configureAppVersion
import com.threegap.bitnagil.convention.extension.configureComposeAndroid
import com.threegap.bitnagil.convention.extension.configureKotlinAndroid
import org.gradle.api.Plugin
Expand All @@ -21,10 +22,10 @@ class AndroidApplicationPlugin : Plugin<Project> {
extensions.configure<ApplicationExtension> {
configureKotlinAndroid(this)
configureComposeAndroid(this)
configureAppVersion()
with(defaultConfig) {
targetSdk = libs.findVersion("targetSdk").get().requiredVersion.toInt()
versionCode = libs.findVersion("versionCode").get().requiredVersion.toInt()
versionName = libs.findVersion("versionName").get().requiredVersion
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.threegap.bitnagil.convention

import com.android.build.gradle.LibraryExtension
import com.threegap.bitnagil.convention.extension.configureAppVersion
import com.threegap.bitnagil.convention.extension.configureKotlinAndroid
import com.threegap.bitnagil.convention.extension.configureKotlinCoroutine
import org.gradle.api.Plugin
Expand All @@ -17,6 +18,7 @@ class AndroidLibraryPlugin : Plugin<Project> {
extensions.configure<LibraryExtension> {
configureKotlinAndroid(this)
configureKotlinCoroutine(this)
configureAppVersion()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.threegap.bitnagil.convention.extension

import com.android.build.api.dsl.ApplicationExtension
import com.android.build.api.dsl.LibraryExtension
import org.gradle.api.Project
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.kotlin.dsl.findByType
import org.gradle.kotlin.dsl.getByType

internal fun Project.configureAppVersion() {
val libs = extensions.getByType<VersionCatalogsExtension>().named("libs")
val major = libs.findVersion("versionMajor").get().requiredVersion
val minor = libs.findVersion("versionMinor").get().requiredVersion
val patch = libs.findVersion("versionPatch").get().requiredVersion

extensions.findByType<ApplicationExtension>()?.let { appExtension ->
appExtension.defaultConfig {
versionName = "$major.$minor.$patch"
buildConfigField("int", "VERSION_MAJOR", major)
buildConfigField("int", "VERSION_MINOR", minor)
buildConfigField("int", "VERSION_PATCH", patch)
}
}

extensions.findByType<LibraryExtension>()?.let { libExtension ->
libExtension.apply {
defaultConfig {
buildConfigField("int", "VERSION_MAJOR", major)
buildConfigField("int", "VERSION_MINOR", minor)
buildConfigField("int", "VERSION_PATCH", patch)
}
buildFeatures {
buildConfig = true
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.threegap.bitnagil.data.version.datasource

import com.threegap.bitnagil.data.version.model.response.VersionCheckResponseDto

interface VersionDataSource {
suspend fun checkVersion(): Result<VersionCheckResponseDto>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.threegap.bitnagil.data.version.datasourceImpl

import com.threegap.bitnagil.data.BuildConfig
import com.threegap.bitnagil.data.common.safeApiCall
import com.threegap.bitnagil.data.version.datasource.VersionDataSource
import com.threegap.bitnagil.data.version.model.response.VersionCheckResponseDto
import com.threegap.bitnagil.data.version.service.VersionService
import javax.inject.Inject

class VersionDataSourceImpl @Inject constructor(
private val versionService: VersionService,
) : VersionDataSource {

override suspend fun checkVersion(): Result<VersionCheckResponseDto> =
safeApiCall {
versionService.checkVersion(
majorVersion = BuildConfig.VERSION_MAJOR,
minorVersion = BuildConfig.VERSION_MINOR,
patchVersion = BuildConfig.VERSION_PATCH,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.threegap.bitnagil.data.version.model.response

import com.threegap.bitnagil.domain.version.model.UpdateRequirement
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class VersionCheckResponseDto(
@SerialName("forceUpdateYn")
val forceUpdateYn: Boolean,
)

fun VersionCheckResponseDto.toDomain() =
UpdateRequirement(
isForced = this.forceUpdateYn,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.threegap.bitnagil.data.version.repositoryImpl

import com.threegap.bitnagil.data.version.datasource.VersionDataSource
import com.threegap.bitnagil.data.version.model.response.toDomain
import com.threegap.bitnagil.domain.version.model.UpdateRequirement
import com.threegap.bitnagil.domain.version.repository.VersionRepository
import javax.inject.Inject

class VersionRepositoryImpl @Inject constructor(
private val versionDataSource: VersionDataSource,
) : VersionRepository {

override suspend fun checkVersion(): Result<UpdateRequirement> =
versionDataSource.checkVersion().map { it.toDomain() }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.threegap.bitnagil.data.version.service

import com.threegap.bitnagil.data.version.model.response.VersionCheckResponseDto
import com.threegap.bitnagil.network.model.BaseResponse
import retrofit2.http.GET
import retrofit2.http.Query

interface VersionService {
@GET("/api/v1/version/android/check")
suspend fun checkVersion(
@Query("major") majorVersion: Int,
@Query("minor") minorVersion: Int,
@Query("patch") patchVersion: Int,
): BaseResponse<VersionCheckResponseDto>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.threegap.bitnagil.domain.version.model

data class UpdateRequirement(
val isForced: Boolean,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.threegap.bitnagil.domain.version.repository

import com.threegap.bitnagil.domain.version.model.UpdateRequirement

interface VersionRepository {
suspend fun checkVersion(): Result<UpdateRequirement>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.threegap.bitnagil.domain.version.usecase

import com.threegap.bitnagil.domain.version.repository.VersionRepository
import javax.inject.Inject

class CheckUpdateRequirementUseCase @Inject constructor(
private val versionRepository: VersionRepository,
) {
suspend operator fun invoke(): Result<Boolean> =
versionRepository.checkVersion().map { it.isForced }
}
4 changes: 3 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ targetSdk = "35"

## App Versioning
versionCode = "4"
versionName = "1.0.2"
versionMajor = "1"
versionMinor = "0"
versionPatch = "2"

# Android Gradle Plugin
androidGradlePlugin = "8.10.1"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.threegap.bitnagil.presentation.splash

import androidx.activity.ComponentActivity
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.foundation.background
Expand All @@ -16,15 +17,20 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.threegap.bitnagil.designsystem.BitnagilTheme
import com.threegap.bitnagil.designsystem.R
import com.threegap.bitnagil.designsystem.component.atom.BitnagilIcon
import com.threegap.bitnagil.presentation.splash.component.template.BitnagilLottieAnimation
import com.threegap.bitnagil.presentation.splash.component.template.ForceUpdateDialog
import com.threegap.bitnagil.presentation.splash.model.SplashSideEffect
import com.threegap.bitnagil.presentation.splash.util.openAppInPlayStore
import org.orbitmvi.orbit.compose.collectSideEffect
import kotlin.system.exitProcess

@Composable
fun SplashScreenContainer(
Expand All @@ -34,6 +40,10 @@ fun SplashScreenContainer(
navigateToHome: () -> Unit,
viewModel: SplashViewModel = hiltViewModel(),
) {
val context = LocalContext.current
val activity = context as? ComponentActivity
val uiState by viewModel.stateFlow.collectAsStateWithLifecycle()

viewModel.collectSideEffect { sideEffect ->
when (sideEffect) {
is SplashSideEffect.NavigateToLogin -> navigateToLogin()
Expand All @@ -46,6 +56,16 @@ fun SplashScreenContainer(
SplashScreen(
onCompleted = viewModel::onAnimationCompleted,
)

if (uiState.forceUpdateRequired) {
ForceUpdateDialog(
onUpdateClick = { openAppInPlayStore(activity) },
onDismissRequest = {
activity?.finishAffinity()
exitProcess(0)
},
)
}
}

@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.threegap.bitnagil.domain.auth.model.UserRole
import com.threegap.bitnagil.domain.auth.usecase.AutoLoginUseCase
import com.threegap.bitnagil.domain.version.usecase.CheckUpdateRequirementUseCase
import com.threegap.bitnagil.presentation.common.mviviewmodel.MviViewModel
import com.threegap.bitnagil.presentation.splash.model.SplashIntent
import com.threegap.bitnagil.presentation.splash.model.SplashSideEffect
Expand All @@ -18,14 +19,15 @@ import javax.inject.Inject
@HiltViewModel
class SplashViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
private val checkUpdateRequirementUseCase: CheckUpdateRequirementUseCase,
private val autoLoginUseCase: AutoLoginUseCase,
) : MviViewModel<SplashState, SplashSideEffect, SplashIntent>(
initState = SplashState(),
savedStateHandle = savedStateHandle,
) {

init {
performAutoLogin()
performForceUpdateCheck()
}

override suspend fun SimpleSyntax<SplashState, SplashSideEffect>.reduceState(
Expand All @@ -40,6 +42,13 @@ class SplashViewModel @Inject constructor(
)
}

is SplashIntent.SetForceUpdateResult -> {
state.copy(
forceUpdateRequired = intent.isRequired,
isForceUpdateCheckCompleted = true,
)
}

is SplashIntent.NavigateToLogin -> {
sendSideEffect(SplashSideEffect.NavigateToLogin)
null
Expand All @@ -61,6 +70,20 @@ class SplashViewModel @Inject constructor(
}
}

private fun performForceUpdateCheck() {
viewModelScope.launch {
val isUpdateRequired = withTimeoutOrNull(5000) {
checkUpdateRequirementUseCase().getOrElse { false }
} ?: false

sendIntent(SplashIntent.SetForceUpdateResult(isUpdateRequired))

if (!isUpdateRequired) {
performAutoLogin()
}
}
}

private fun performAutoLogin() {
viewModelScope.launch {
try {
Expand All @@ -76,6 +99,9 @@ class SplashViewModel @Inject constructor(

fun onAnimationCompleted() {
val splashState = container.stateFlow.value

if (splashState.forceUpdateRequired) return

if (!splashState.isAutoLoginCompleted) {
viewModelScope.launch {
delay(100)
Expand Down
Loading