diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ce08a10e..552b58c7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -28,6 +28,9 @@ android:theme="@style/Theme.PeekabookAOS" android:usesCleartextTraffic="true" tools:targetApi="31"> + = + kotlin.runCatching { forceUpdateDataSource.getVersion() }.map { response -> + requireNotNull(response.data).toVersion() + } +} diff --git a/app/src/main/java/com/sopt/peekabookaos/data/service/ForceUpdateService.kt b/app/src/main/java/com/sopt/peekabookaos/data/service/ForceUpdateService.kt new file mode 100644 index 00000000..13b32c72 --- /dev/null +++ b/app/src/main/java/com/sopt/peekabookaos/data/service/ForceUpdateService.kt @@ -0,0 +1,10 @@ +package com.sopt.peekabookaos.data.service + +import com.sopt.peekabookaos.data.entity.BaseResponse +import com.sopt.peekabookaos.data.entity.response.VersionResponse +import retrofit2.http.GET + +interface ForceUpdateService { + @GET("user/v1/version") + suspend fun getVersion(): BaseResponse +} diff --git a/app/src/main/java/com/sopt/peekabookaos/data/source/remote/ForceUpdateDataSource.kt b/app/src/main/java/com/sopt/peekabookaos/data/source/remote/ForceUpdateDataSource.kt new file mode 100644 index 00000000..c21ee921 --- /dev/null +++ b/app/src/main/java/com/sopt/peekabookaos/data/source/remote/ForceUpdateDataSource.kt @@ -0,0 +1,13 @@ +package com.sopt.peekabookaos.data.source.remote + +import com.sopt.peekabookaos.data.entity.BaseResponse +import com.sopt.peekabookaos.data.entity.response.VersionResponse +import com.sopt.peekabookaos.data.service.ForceUpdateService +import javax.inject.Inject + +class ForceUpdateDataSource @Inject constructor( + private val forceUpdateService: ForceUpdateService +) { + suspend fun getVersion(): BaseResponse = + forceUpdateService.getVersion() +} diff --git a/app/src/main/java/com/sopt/peekabookaos/di/RepositoryModule.kt b/app/src/main/java/com/sopt/peekabookaos/di/RepositoryModule.kt index 78a3a0bc..0b8c200f 100644 --- a/app/src/main/java/com/sopt/peekabookaos/di/RepositoryModule.kt +++ b/app/src/main/java/com/sopt/peekabookaos/di/RepositoryModule.kt @@ -4,6 +4,7 @@ import com.sopt.peekabookaos.data.repository.AuthRepositoryImpl import com.sopt.peekabookaos.data.repository.BlockRepositoryImpl import com.sopt.peekabookaos.data.repository.BookRepositoryImpl import com.sopt.peekabookaos.data.repository.DetailRepositoryImpl +import com.sopt.peekabookaos.data.repository.ForceUpdateRepositoryImpl import com.sopt.peekabookaos.data.repository.MyPageRepositoryImpl import com.sopt.peekabookaos.data.repository.NaverRepositoryImpl import com.sopt.peekabookaos.data.repository.NotificationRepositoryImpl @@ -17,6 +18,7 @@ import com.sopt.peekabookaos.domain.repository.AuthRepository import com.sopt.peekabookaos.domain.repository.BlockRepository import com.sopt.peekabookaos.domain.repository.BookRepository import com.sopt.peekabookaos.domain.repository.DetailRepository +import com.sopt.peekabookaos.domain.repository.ForceUpdateRepository import com.sopt.peekabookaos.domain.repository.MyPageRepository import com.sopt.peekabookaos.domain.repository.NaverRepository import com.sopt.peekabookaos.domain.repository.NotificationRepository @@ -112,4 +114,10 @@ abstract class RepositoryModule { abstract fun bindToMyPageRepository( myPageRepositoryImpl: MyPageRepositoryImpl ): MyPageRepository + + @Binds + @Singleton + abstract fun bindToForceUpdateRepository( + forceUpdateRepositoryImpl: ForceUpdateRepositoryImpl + ): ForceUpdateRepository } diff --git a/app/src/main/java/com/sopt/peekabookaos/di/RetrofitServiceModule.kt b/app/src/main/java/com/sopt/peekabookaos/di/RetrofitServiceModule.kt index e3a5342a..22f4b3fc 100644 --- a/app/src/main/java/com/sopt/peekabookaos/di/RetrofitServiceModule.kt +++ b/app/src/main/java/com/sopt/peekabookaos/di/RetrofitServiceModule.kt @@ -4,6 +4,7 @@ import com.sopt.peekabookaos.data.service.AuthService import com.sopt.peekabookaos.data.service.BlockService import com.sopt.peekabookaos.data.service.BookService import com.sopt.peekabookaos.data.service.DetailService +import com.sopt.peekabookaos.data.service.ForceUpdateService import com.sopt.peekabookaos.data.service.MyPageService import com.sopt.peekabookaos.data.service.NaverService import com.sopt.peekabookaos.data.service.NotificationService @@ -21,6 +22,7 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import retrofit2.Retrofit +import retrofit2.create @Module @InstallIn(SingletonComponent::class) @@ -76,4 +78,8 @@ object RetrofitServiceModule { @Provides fun providesReportService(@PeekaType retrofit: Retrofit): ReportService = retrofit.create(ReportService::class.java) + + @Provides + fun providesForceUpdateService(@PeekaType retrofit: Retrofit): ForceUpdateService = + retrofit.create(ForceUpdateService::class.java) } diff --git a/app/src/main/java/com/sopt/peekabookaos/domain/entity/Version.kt b/app/src/main/java/com/sopt/peekabookaos/domain/entity/Version.kt new file mode 100644 index 00000000..c998453d --- /dev/null +++ b/app/src/main/java/com/sopt/peekabookaos/domain/entity/Version.kt @@ -0,0 +1,11 @@ +package com.sopt.peekabookaos.domain.entity + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +data class Version( + val imageUrl: String = "", + val androidForceVersion: String = "", + val versionText: String = "" +) : Parcelable diff --git a/app/src/main/java/com/sopt/peekabookaos/domain/entity/VersionDetail.kt b/app/src/main/java/com/sopt/peekabookaos/domain/entity/VersionDetail.kt new file mode 100644 index 00000000..b904357a --- /dev/null +++ b/app/src/main/java/com/sopt/peekabookaos/domain/entity/VersionDetail.kt @@ -0,0 +1,6 @@ +package com.sopt.peekabookaos.domain.entity + +data class VersionDetail( + val major: String, + val minor: String +) diff --git a/app/src/main/java/com/sopt/peekabookaos/domain/entity/VersionState.kt b/app/src/main/java/com/sopt/peekabookaos/domain/entity/VersionState.kt new file mode 100644 index 00000000..8bb1ca7b --- /dev/null +++ b/app/src/main/java/com/sopt/peekabookaos/domain/entity/VersionState.kt @@ -0,0 +1,5 @@ +package com.sopt.peekabookaos.domain.entity + +enum class VersionState { + LATEST, OUTDATED +} diff --git a/app/src/main/java/com/sopt/peekabookaos/domain/repository/ForceUpdateRepository.kt b/app/src/main/java/com/sopt/peekabookaos/domain/repository/ForceUpdateRepository.kt new file mode 100644 index 00000000..6050968a --- /dev/null +++ b/app/src/main/java/com/sopt/peekabookaos/domain/repository/ForceUpdateRepository.kt @@ -0,0 +1,7 @@ +package com.sopt.peekabookaos.domain.repository + +import com.sopt.peekabookaos.domain.entity.Version + +interface ForceUpdateRepository { + suspend fun getVersion(): Result +} diff --git a/app/src/main/java/com/sopt/peekabookaos/domain/usecase/GetVersionUseCase.kt b/app/src/main/java/com/sopt/peekabookaos/domain/usecase/GetVersionUseCase.kt new file mode 100644 index 00000000..e4884600 --- /dev/null +++ b/app/src/main/java/com/sopt/peekabookaos/domain/usecase/GetVersionUseCase.kt @@ -0,0 +1,10 @@ +package com.sopt.peekabookaos.domain.usecase + +import com.sopt.peekabookaos.domain.repository.ForceUpdateRepository +import javax.inject.Inject + +class GetVersionUseCase @Inject constructor( + private val forceUpdateRepository: ForceUpdateRepository +) { + suspend operator fun invoke() = forceUpdateRepository.getVersion() +} diff --git a/app/src/main/java/com/sopt/peekabookaos/presentation/forceUpdate/ForceUpdateActivity.kt b/app/src/main/java/com/sopt/peekabookaos/presentation/forceUpdate/ForceUpdateActivity.kt new file mode 100644 index 00000000..c9e3eae1 --- /dev/null +++ b/app/src/main/java/com/sopt/peekabookaos/presentation/forceUpdate/ForceUpdateActivity.kt @@ -0,0 +1,40 @@ +package com.sopt.peekabookaos.presentation.forceUpdate + +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import androidx.activity.viewModels +import com.sopt.peekabookaos.R +import com.sopt.peekabookaos.databinding.ActivityForceUpdateBinding +import com.sopt.peekabookaos.domain.entity.Version +import com.sopt.peekabookaos.presentation.splash.SplashActivity.Companion.LATEST_VERSION +import com.sopt.peekabookaos.util.binding.BindingActivity +import com.sopt.peekabookaos.util.extensions.getParcelable + +class ForceUpdateActivity : + BindingActivity(R.layout.activity_force_update) { + private lateinit var intentToPlayStore: Intent + private val viewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding.viewModel = viewModel + getLatestVersion() + initUpdateBtnClickListener() + } + + private fun initUpdateBtnClickListener() { + binding.btnForceUpdate.setOnClickListener { + intentToPlayStore = Intent( + Intent.ACTION_VIEW, + Uri.parse(getString(R.string.force_update_store_link)) + ) + startActivity(intentToPlayStore) + } + } + + private fun getLatestVersion() { + intent.getParcelable(LATEST_VERSION, Version::class.java) + ?.let { viewModel.getLatestVersion(it) } + } +} diff --git a/app/src/main/java/com/sopt/peekabookaos/presentation/forceUpdate/ForceUpdateViewModel.kt b/app/src/main/java/com/sopt/peekabookaos/presentation/forceUpdate/ForceUpdateViewModel.kt new file mode 100644 index 00000000..992bb827 --- /dev/null +++ b/app/src/main/java/com/sopt/peekabookaos/presentation/forceUpdate/ForceUpdateViewModel.kt @@ -0,0 +1,15 @@ +package com.sopt.peekabookaos.presentation.forceUpdate + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.sopt.peekabookaos.domain.entity.Version + +class ForceUpdateViewModel : ViewModel() { + private val _latestVersion: MutableLiveData = MutableLiveData() + val latestVersion: LiveData = _latestVersion + + fun getLatestVersion(version: Version) { + _latestVersion.value = version + } +} diff --git a/app/src/main/java/com/sopt/peekabookaos/presentation/splash/SplashActivity.kt b/app/src/main/java/com/sopt/peekabookaos/presentation/splash/SplashActivity.kt index d829d028..e28ccd88 100644 --- a/app/src/main/java/com/sopt/peekabookaos/presentation/splash/SplashActivity.kt +++ b/app/src/main/java/com/sopt/peekabookaos/presentation/splash/SplashActivity.kt @@ -2,6 +2,8 @@ package com.sopt.peekabookaos.presentation.splash import android.annotation.SuppressLint import android.content.Intent +import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK +import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.os.Bundle import android.os.Handler import android.os.Looper @@ -9,6 +11,8 @@ import androidx.activity.viewModels import com.sopt.peekabookaos.R import com.sopt.peekabookaos.databinding.ActivitySplashBinding import com.sopt.peekabookaos.domain.entity.SplashState +import com.sopt.peekabookaos.domain.entity.VersionState +import com.sopt.peekabookaos.presentation.forceUpdate.ForceUpdateActivity import com.sopt.peekabookaos.presentation.main.MainActivity import com.sopt.peekabookaos.presentation.onboarding.OnboardingActivity import com.sopt.peekabookaos.util.binding.BindingActivity @@ -22,7 +26,28 @@ class SplashActivity : BindingActivity(R.layout.activity_ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding.lottieSplash.playAnimation() - Handler(Looper.getMainLooper()).postDelayed({ checkSplashState() }, DURATION) + Handler(Looper.getMainLooper()).postDelayed({ initIsForceUpdateObserver() }, DURATION) + } + + private fun initIsForceUpdateObserver() { + splashViewModel.latestVersion.observe(this) { + splashViewModel.checkUpdateVersion() + checkVersionUpdate() + } + } + + private fun checkVersionUpdate() { + when (splashViewModel.checkUpdateVersion()) { + VersionState.LATEST -> checkSplashState() + VersionState.OUTDATED -> { + val intentToForceUpdate = Intent(this, ForceUpdateActivity::class.java).apply { + putExtra(LATEST_VERSION, splashViewModel.latestVersion.value) + addFlags(FLAG_ACTIVITY_CLEAR_TASK or FLAG_ACTIVITY_NEW_TASK) + } + startActivity(Intent(intentToForceUpdate)) + finish() + } + } } private fun checkSplashState() { @@ -36,5 +61,6 @@ class SplashActivity : BindingActivity(R.layout.activity_ companion object { private const val DURATION: Long = 2000 + const val LATEST_VERSION = "latest version" } } diff --git a/app/src/main/java/com/sopt/peekabookaos/presentation/splash/SplashViewModel.kt b/app/src/main/java/com/sopt/peekabookaos/presentation/splash/SplashViewModel.kt index faf1b808..268ae523 100644 --- a/app/src/main/java/com/sopt/peekabookaos/presentation/splash/SplashViewModel.kt +++ b/app/src/main/java/com/sopt/peekabookaos/presentation/splash/SplashViewModel.kt @@ -1,14 +1,65 @@ package com.sopt.peekabookaos.presentation.splash +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.sopt.peekabookaos.BuildConfig import com.sopt.peekabookaos.domain.entity.SplashState +import com.sopt.peekabookaos.domain.entity.Version +import com.sopt.peekabookaos.domain.entity.VersionDetail +import com.sopt.peekabookaos.domain.entity.VersionState import com.sopt.peekabookaos.domain.usecase.GetSplashStateUseCase +import com.sopt.peekabookaos.domain.usecase.GetVersionUseCase import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject @HiltViewModel class SplashViewModel @Inject constructor( - private val getSplashStateUseCase: GetSplashStateUseCase + private val getSplashStateUseCase: GetSplashStateUseCase, + private val getVersionUseCase: GetVersionUseCase ) : ViewModel() { + private val _latestVersion: MutableLiveData = MutableLiveData() + val latestVersion: LiveData = _latestVersion + private lateinit var latestVersionDetail: VersionDetail + private lateinit var appVersionDetail: VersionDetail + + init { + getVersion() + } + fun getSplashState(): SplashState = getSplashStateUseCase() + + fun checkUpdateVersion(): VersionState { + latestVersionDetail = + spiltVersionToMajorMinor(requireNotNull(latestVersion.value?.androidForceVersion) { "version is null" }) + appVersionDetail = spiltVersionToMajorMinor(BuildConfig.VERSION_NAME) + return if (appVersionDetail.major != latestVersionDetail.major || appVersionDetail.minor != latestVersionDetail.minor) VersionState.OUTDATED + else VersionState.LATEST + } + + private fun spiltVersionToMajorMinor(versionName: String): VersionDetail { + val versionSpiltList = versionName.split(".") + val major = versionSpiltList[0] + val minor = versionSpiltList[1] + return VersionDetail(major, minor) + } + + private fun getVersion() { + viewModelScope.launch { + getVersionUseCase() + .onSuccess { response -> + _latestVersion.value = Version( + response.imageUrl, + response.androidForceVersion, + response.versionText + ) + } + .onFailure { throwable -> + Timber.e("$throwable") + } + } + } } diff --git a/app/src/main/res/layout/activity_force_update.xml b/app/src/main/res/layout/activity_force_update.xml new file mode 100644 index 00000000..666f325e --- /dev/null +++ b/app/src/main/res/layout/activity_force_update.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4eaa93b8..ed824201 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -206,4 +206,9 @@ 한 줄 소개는 40자까지 쓸 수 있어요! 차단된 사용자가 없어요. yyyyMMdd_HHmmss + + + 앗! 피카북이 달라졌어요 + 업데이트 하러 가기 + https://play.google.com/store/apps/details?id=com.sopt.peekabookaos&hl=ko-KR \ No newline at end of file