UI 변경, 광고 나오는 타임 변경 -> 30초 이후 클리어시 에서 -> 5분 타이머가 만료되는 스테이지 클리어 시.
This commit is contained in:
@@ -29,6 +29,7 @@ class UserPreferencesManager(private val context: Context) {
|
||||
private val KEY_SOUND_ENABLED = booleanPreferencesKey("sound_enabled")
|
||||
private val KEY_VIBRATION_ENABLED = booleanPreferencesKey("vibration_enabled")
|
||||
private val KEY_PRE_LEARNED_WORDS = stringSetPreferencesKey("pre_learned_words")
|
||||
private val KEY_ACCUMULATED_PUZZLE_TIME_SEC = longPreferencesKey("accumulated_puzzle_time_sec")
|
||||
}
|
||||
|
||||
private fun getTodayDateString(): String {
|
||||
@@ -158,4 +159,30 @@ class UserPreferencesManager(private val context: Context) {
|
||||
preferences[KEY_PRE_LEARNED_WORDS] = currentSet
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 활성 퍼즐 풀이 시간을 1초 추가합니다.
|
||||
*/
|
||||
suspend fun addPuzzleTime(addedSeconds: Long = 1L) {
|
||||
context.dataStore.edit { preferences ->
|
||||
val current = preferences[KEY_ACCUMULATED_PUZZLE_TIME_SEC] ?: 0L
|
||||
preferences[KEY_ACCUMULATED_PUZZLE_TIME_SEC] = current + addedSeconds
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 누적 퍼즐 풀이 시간이 5분(300초) 이상인지 확인합니다.
|
||||
* 300초 이상일 경우 true를 반환하고, 새 카운트를 위해 누적 시간을 0초로 초기화합니다.
|
||||
*/
|
||||
suspend fun checkAndResetAdPuzzleTime(): Boolean {
|
||||
var shouldShowAd = false
|
||||
context.dataStore.edit { preferences ->
|
||||
val current = preferences[KEY_ACCUMULATED_PUZZLE_TIME_SEC] ?: 0L
|
||||
if (current >= 300L) { // 5분 = 300초
|
||||
preferences[KEY_ACCUMULATED_PUZZLE_TIME_SEC] = 0L
|
||||
shouldShowAd = true
|
||||
}
|
||||
}
|
||||
return shouldShowAd
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,24 +64,14 @@ fun GameScreen(
|
||||
}
|
||||
}
|
||||
|
||||
// 클리어 이벤트 수신: 상단 메시지 토스트 출력 후 다음 스테이지 전환
|
||||
// 클리어 이벤트 수신: 누적 퍼즐 풀이 시간이 5분(300초) 이상이면 다음 스테이지 진입 전 전체화면 광고 노출 후 5분 카운트 리셋
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.stageClearEvent.collect { event ->
|
||||
if (event.isLastStage) {
|
||||
ToastUtils.showTopToast(context, "🎉 모든 스테이지를 클리어하셨습니다!")
|
||||
onNavigateBack()
|
||||
} else if (event.isFastClear) {
|
||||
ToastUtils.showTopToast(
|
||||
context,
|
||||
"⚡ ${event.elapsedSeconds}초 만에 빠른 클리어! (광고 없이 통과)"
|
||||
)
|
||||
onNavigateToNextStage(event.nextStageNumber)
|
||||
} else {
|
||||
ToastUtils.showTopToast(
|
||||
context,
|
||||
"클리어! (소요 시간: ${event.elapsedSeconds}초)"
|
||||
)
|
||||
|
||||
} else if (event.shouldShowAd) {
|
||||
ToastUtils.showTopToast(context, "🎉 클리어! (소요 시간: ${event.elapsedSeconds}초)")
|
||||
if (activity != null) {
|
||||
AdMobManager.showInterstitialAd(
|
||||
activity = activity,
|
||||
@@ -91,6 +81,9 @@ fun GameScreen(
|
||||
} else {
|
||||
onNavigateToNextStage(event.nextStageNumber)
|
||||
}
|
||||
} else {
|
||||
ToastUtils.showTopToast(context, "🎉 클리어! (소요 시간: ${event.elapsedSeconds}초)")
|
||||
onNavigateToNextStage(event.nextStageNumber)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,9 @@ import com.wordpuzzle.hangul.data.repository.StageRepository
|
||||
import com.wordpuzzle.hangul.data.repository.WordRepository
|
||||
import com.wordpuzzle.hangul.domain.engine.TilePoolGenerator
|
||||
import com.wordpuzzle.hangul.domain.usecase.SolveTileUseCase
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
data class GameUiState(
|
||||
@@ -33,7 +35,7 @@ data class StageClearEvent(
|
||||
val nextStageNumber: Int,
|
||||
val isLastStage: Boolean,
|
||||
val elapsedSeconds: Long,
|
||||
val isFastClear: Boolean
|
||||
val shouldShowAd: Boolean = false
|
||||
)
|
||||
|
||||
class GameViewModel(application: Application) : AndroidViewModel(application) {
|
||||
@@ -73,6 +75,26 @@ class GameViewModel(application: Application) : AndroidViewModel(application) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5분(300초) 순수 퍼즐 풀이 시간 초 단위 누적 타이머 (게임 도중 팝업 없이 누적만 수행)
|
||||
viewModelScope.launch {
|
||||
while (isActive) {
|
||||
delay(1000L)
|
||||
if (isActivelyPlayingPuzzle()) {
|
||||
prefs.addPuzzleTime(1L)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isActivelyPlayingPuzzle(): Boolean {
|
||||
val state = _uiState.value
|
||||
return !isTimerPaused &&
|
||||
state.stage != null &&
|
||||
!state.isLoading &&
|
||||
!state.isStageClear &&
|
||||
!state.isStageFailed &&
|
||||
!state.showHintDialog
|
||||
}
|
||||
|
||||
fun loadStage(stageNumber: Int) {
|
||||
@@ -322,7 +344,6 @@ class GameViewModel(application: Application) : AndroidViewModel(application) {
|
||||
private fun handleStageClear(stage: CrosswordStage) {
|
||||
viewModelScope.launch {
|
||||
val elapsedSeconds = getEffectiveElapsedSeconds()
|
||||
val isFastClear = elapsedSeconds <= 30L
|
||||
|
||||
prefs.setStageCleared(stage.stageNumber)
|
||||
for (clue in stage.clues) {
|
||||
@@ -332,13 +353,16 @@ class GameViewModel(application: Application) : AndroidViewModel(application) {
|
||||
val totalStages = stageRepo.getTotalStageCount()
|
||||
val isLastStage = stage.stageNumber >= totalStages
|
||||
|
||||
// 누적 퍼즐 풀이 시간이 5분(300초) 이상인지 검증하고, 이상일 경우 카운트 0초 초기화 후 광고 노출 플래그 설정
|
||||
val shouldShowAd = prefs.checkAndResetAdPuzzleTime()
|
||||
|
||||
_stageClearEvent.emit(
|
||||
StageClearEvent(
|
||||
stageNumber = stage.stageNumber,
|
||||
nextStageNumber = stage.stageNumber + 1,
|
||||
isLastStage = isLastStage,
|
||||
elapsedSeconds = elapsedSeconds,
|
||||
isFastClear = isFastClear
|
||||
shouldShowAd = shouldShowAd
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user