Skip to content

Commit

Permalink
Merge pull request #99 from Next-Room/feature/password
Browse files Browse the repository at this point in the history
관리자 코드를 제거하고 앱 비밀번호를 사용하여 인증하는 방식 구현
  • Loading branch information
juhwankim-dev authored Jan 16, 2025
2 parents 5811625 + 0691cb6 commit 705f245
Show file tree
Hide file tree
Showing 31 changed files with 1,015 additions and 291 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import com.nextroom.nextroom.data.db.AppSettings
import com.nextroom.nextroom.data.db.dataStore
import com.nextroom.nextroom.data.network.ApiService
import com.nextroom.nextroom.data.network.request.LoginRequest
import com.nextroom.nextroom.data.network.response.toDomain
import com.nextroom.nextroom.domain.model.LoginInfo
import com.nextroom.nextroom.domain.model.Result
import com.nextroom.nextroom.domain.model.mapOnSuccess
Expand Down Expand Up @@ -43,6 +42,7 @@ class AuthDataSource @Inject constructor(
shopName = "",
accessToken = "",
refreshToken = "",
appPassword = "",
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,19 @@ class SettingDataSource @Inject constructor(

fun saveUserEmail(userEmail: String) = runBlocking {
dataStore.updateData {
it.copy(userEmail= userEmail)
it.copy(userEmail = userEmail)
}
}

fun getUserEmail() = runBlocking {
data.first().userEmail
}

suspend fun saveAppPassword(password: String) {
dataStore.updateData {
it.copy(appPassword = password)
}
}

suspend fun getAppPassword() = data.first().appPassword
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ data class AppSettings(
val lastLaunchDate: Long = 0L,
val emailSaveChecked: Boolean = false,
val userEmail: String = "",
val networkDisconnectedCount: Int = 0
val networkDisconnectedCount: Int = 0,
val appPassword: String = "",
)
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,12 @@ class AdminRepositoryImpl @Inject constructor(
override suspend fun getUserEmail(): String {
return settingDataSource.getUserEmail()
}

override suspend fun saveAppPassword(password: String) {
return settingDataSource.saveAppPassword(password)
}

override suspend fun getAppPassword(): String {
return settingDataSource.getAppPassword()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,6 @@ interface AdminRepository {
suspend fun getUserSubscribe(): Result<Mypage>
suspend fun getEmailSaveChecked(): Boolean
suspend fun getUserEmail(): String
suspend fun saveAppPassword(password: String)
suspend fun getAppPassword(): String
}
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ firebase-analytics="22.1.2"
swiperefreshlayout = "1.1.0"
firebaseCommonKtx = "21.0.0"
photoview = "2.3.0"
biometric = "1.1.0"

[libraries]
androidx-activity-ktx = { module = "androidx.activity:activity-ktx", version.ref = "activity-ktx" }
Expand Down Expand Up @@ -96,6 +97,7 @@ timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" }
billing = { module = "com.android.billingclient:billing", version.ref = "billing" }
androidx-swiperefreshlayout = { group = "androidx.swiperefreshlayout", name = "swiperefreshlayout", version.ref = "swiperefreshlayout" }
firebase-common-ktx = { group = "com.google.firebase", name = "firebase-common-ktx", version.ref = "firebaseCommonKtx" }
biometric = { module = "androidx.biometric:biometric", version.ref = "biometric" }

[plugins]
android-application = { id = "com.android.application", version.ref = "android" }
Expand Down
1 change: 1 addition & 0 deletions presentation/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ dependencies {
implementation(libs.timber)
implementation(libs.billing)
implementation(libs.photoview)
implementation(libs.biometric)

implementation(platform(libs.firebase.bom))
implementation(libs.firebase.analytics.ktx)
Expand Down
1 change: 1 addition & 0 deletions presentation/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.nextroom.nextroom.presentation.extension

import android.os.Bundle

fun Bundle.hasResultData() = this.containsKey(BUNDLE_KEY_RESULT_DATA)

fun Bundle.getResultData() = this.getString(BUNDLE_KEY_RESULT_DATA)

const val BUNDLE_KEY_RESULT_DATA = "BUNDLE_KEY_RESULT_DATA"
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package com.nextroom.nextroom.presentation.ui.adminmain

import com.nextroom.nextroom.domain.model.SubscribeStatus

sealed interface AdminMainEvent {
data object NetworkError : AdminMainEvent
data object UnknownError : AdminMainEvent
data class ClientError(val message: String) : AdminMainEvent
data object InAppReview : AdminMainEvent
data class ReadyToGameStart(val subscribeStatus: SubscribeStatus) : AdminMainEvent
data object NeedToSetPassword : AdminMainEvent
data class NeedToCheckPasswordForStartGame(val themeId: String) : AdminMainEvent
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,22 @@ import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.core.os.bundleOf
import androidx.core.view.isVisible
import androidx.fragment.app.setFragmentResultListener
import androidx.fragment.app.viewModels
import androidx.navigation.NavOptions
import androidx.navigation.fragment.findNavController
import com.google.android.play.core.review.ReviewManagerFactory
import com.google.firebase.analytics.FirebaseAnalytics
import com.nextroom.nextroom.domain.model.SubscribeStatus
import com.nextroom.nextroom.domain.repository.StatisticsRepository
import com.nextroom.nextroom.presentation.NavGraphDirections
import com.nextroom.nextroom.presentation.R
import com.nextroom.nextroom.presentation.base.BaseFragment
import com.nextroom.nextroom.presentation.common.NRTwoButtonDialog
import com.nextroom.nextroom.presentation.databinding.FragmentAdminMainBinding
import com.nextroom.nextroom.presentation.extension.addMargin
import com.nextroom.nextroom.presentation.extension.getResultData
import com.nextroom.nextroom.presentation.extension.hasResultData
import com.nextroom.nextroom.presentation.extension.safeNavigate
import com.nextroom.nextroom.presentation.extension.snackbar
import com.nextroom.nextroom.presentation.extension.statusBarHeight
Expand All @@ -40,7 +46,7 @@ class AdminMainFragment :
private val viewModel: AdminMainViewModel by viewModels()
private val adapter: ThemesAdapter by lazy {
ThemesAdapter(
onStartGame = ::startGame,
onThemeClicked = { themeId -> viewModel.onThemeClicked(themeId.toString()) },
onClickUpdate = viewModel::updateTheme,
)
}
Expand All @@ -61,9 +67,34 @@ class AdminMainFragment :
super.onViewCreated(view, savedInstanceState)

initViews()
initSubscribe()
viewModel.observe(viewLifecycleOwner, state = ::render, sideEffect = ::handleEvent)
}

private fun initSubscribe() {
setFragmentResultListener(requestKeyCheckPassword, ::handleFragmentResults)
setFragmentResultListener(dialogKeyNeedToSetPassword, ::handleFragmentResults)
}

private fun handleFragmentResults(requestKey: String, bundle: Bundle) {
when (requestKey) {
requestKeyCheckPassword -> {
try {
if (bundle.hasResultData()) {
bundle.getResultData()?.let { themeId ->
viewModel.tryGameStart(themeId.toInt())
}
}
} catch (e: Exception) {
Timber.e(e)
snackbar(R.string.error_something)
}
}

dialogKeyNeedToSetPassword -> moveToSetPassword()
}
}

override fun onResume() {
super.onResume()
viewModel.onResume()
Expand All @@ -81,12 +112,6 @@ class AdminMainFragment :
}
}*/

private fun startGame(themeId: Int) {
viewModel.start(themeId) {
goToMain(themeId)
}
}

private fun initViews() = with(binding) {
updateSystemPadding(statusBar = false, navigationBar = true)

Expand Down Expand Up @@ -146,6 +171,7 @@ class AdminMainFragment :
tvShopName.text = state.shopName
llEmptyThemeGuide.isVisible = state.themes.isEmpty()
adapter.submitList(state.themes)
binding.layoutOpaqueLoading.root.isVisible = state.opaqueLoading
}

private fun handleEvent(event: AdminMainEvent) {
Expand All @@ -154,6 +180,9 @@ class AdminMainFragment :
is AdminMainEvent.UnknownError -> snackbar(R.string.error_something)
is AdminMainEvent.ClientError -> snackbar(event.message)
AdminMainEvent.InAppReview -> showInAppReview()
is AdminMainEvent.ReadyToGameStart -> moveToGameStart(event.subscribeStatus)
AdminMainEvent.NeedToSetPassword -> showNeedToSetPasswordDialog()
is AdminMainEvent.NeedToCheckPasswordForStartGame -> moveToCheckPasswordForGameStart(event.themeId)
}
}

Expand Down Expand Up @@ -189,13 +218,42 @@ class AdminMainFragment :
findNavController().safeNavigate(action)
}

private fun goToMain(themeId: Int) {
val action =
AdminMainFragmentDirections.actionAdminMainFragmentToVerifyFragment(
themeId = themeId,
subscribeStatus = state.subscribeStatus
)
findNavController().safeNavigate(action)
private fun moveToGameStart(subscribeStatus: SubscribeStatus) {
NavGraphDirections
.actionGlobalGameFragment(subscribeStatus)
.also { findNavController().safeNavigate(it) }
}

private fun showNeedToSetPasswordDialog() {
NavGraphDirections
.actionGlobalNrTwoButtonDialog(
NRTwoButtonDialog.NRTwoButtonArgument(
title = getString(R.string.text_need_to_set_password_title),
message = getString(R.string.text_need_to_set_password_message),
posBtnText = getString(R.string.text_move_to_setting),
negBtnText = getString(R.string.dialog_close),
dialogKey = dialogKeyNeedToSetPassword,
),
).also {
findNavController().safeNavigate(
direction = it,
navOptions = NavOptions.Builder()
.setLaunchSingleTop(true)
.build()
)
}
}

private fun moveToSetPassword() {
NavGraphDirections
.moveToSetPassword()
.also { findNavController().safeNavigate(it) }
}

private fun moveToCheckPasswordForGameStart(themeId: String) {
NavGraphDirections
.moveToCheckPassword(requestKey = requestKeyCheckPassword, resultData = themeId)
.also { findNavController().safeNavigate(it) }
}

override fun onDestroyView() {
Expand All @@ -207,4 +265,9 @@ class AdminMainFragment :
super.onDetach()
backCallback.remove()
}

companion object {
private const val requestKeyCheckPassword = "requestKeyCheckPassword"
private const val dialogKeyNeedToSetPassword = "dialogKeyNeedToSetPassword"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.nextroom.nextroom.presentation.model.ThemeInfoPresentation

data class AdminMainState(
val loading: Boolean = false,
val opaqueLoading: Boolean = false,
val subscribeStatus: SubscribeStatus = SubscribeStatus.Default,
val shopName: String = "",
val themes: List<ThemeInfoPresentation> = emptyList(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,8 @@ import com.nextroom.nextroom.presentation.base.BaseViewModel
import com.nextroom.nextroom.presentation.model.ThemeInfoPresentation
import com.nextroom.nextroom.presentation.model.toPresentation
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.orbitmvi.orbit.Container
import org.orbitmvi.orbit.syntax.simple.intent
import org.orbitmvi.orbit.syntax.simple.postSideEffect
Expand Down Expand Up @@ -48,6 +46,7 @@ class AdminMainViewModel @Inject constructor(

fun onResume() {
loadData()
checkNeedToSetPassword()
}

fun incrementNetworkDisconnectedCount() {
Expand Down Expand Up @@ -90,13 +89,6 @@ class AdminMainViewModel @Inject constructor(
}.onFailure(::handleError)
}

fun start(themeId: Int, readyToStart: () -> Unit) = intent {
themeRepository.updateLatestTheme(themeId)
withContext(Dispatchers.Main) {
readyToStart()
}
}

fun loadData() = intent {
reduce { state.copy(loading = true) }
adminRepository.getUserSubscribe().suspendOnSuccess { myPage ->
Expand Down Expand Up @@ -128,6 +120,37 @@ class AdminMainViewModel @Inject constructor(
reduce { state.copy(themes = themes) }
}

fun tryGameStart(themeId: Int) = intent {
reduce { state.copy(opaqueLoading = true) }
themeRepository.updateLatestTheme(themeId)
adminRepository.getUserSubscribe().suspendOnSuccess { myPage ->
postSideEffect(AdminMainEvent.ReadyToGameStart(myPage.status))
}.onFailure(::handleError)
reduce { state.copy(opaqueLoading = false) }
}

private fun checkNeedToSetPassword() {
viewModelScope.launch {
if (adminRepository.getAppPassword().isEmpty()) {
intent {
postSideEffect(AdminMainEvent.NeedToSetPassword)
}
}
}
}

fun onThemeClicked(themeId: String) {
viewModelScope.launch {
intent {
if (adminRepository.getAppPassword().isEmpty()) {
AdminMainEvent.NeedToSetPassword
} else {
AdminMainEvent.NeedToCheckPasswordForStartGame(themeId)
}.also { postSideEffect(it) }
}
}
}

private fun handleError(error: Result.Failure) = intent {
when (error) {
is Result.Failure.NetworkError -> postSideEffect(AdminMainEvent.NetworkError)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import com.nextroom.nextroom.presentation.databinding.ItemThemeBinding
import com.nextroom.nextroom.presentation.model.ThemeInfoPresentation

class ThemesAdapter(
private val onStartGame: (Int) -> Unit,
private val onThemeClicked: (Int) -> Unit,
private val onClickUpdate: (Int) -> Unit,
) : ListAdapter<ThemeInfoPresentation, ThemesAdapter.ThemeViewHolder>(diffUtil) {

Expand Down Expand Up @@ -44,7 +44,7 @@ class ThemesAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ThemeViewHolder {
return ThemeViewHolder(
ItemThemeBinding.inflate(LayoutInflater.from(parent.context), parent, false),
onStartGame,
onThemeClicked,
onClickUpdate,
)
}
Expand Down
Loading

0 comments on commit 705f245

Please sign in to comment.