Skip to content

Commit c0e045f

Browse files
committed
refactor: move timed sheet logic to specific classes
1 parent a043222 commit c0e045f

File tree

9 files changed

+395
-0
lines changed

9 files changed

+395
-0
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package to.bitkit.di
2+
3+
import dagger.Module
4+
import dagger.Provides
5+
import dagger.hilt.InstallIn
6+
import dagger.hilt.components.SingletonComponent
7+
import kotlinx.coroutines.CoroutineScope
8+
import to.bitkit.utils.timedsheets.TimedSheetManager
9+
10+
@Module
11+
@InstallIn(SingletonComponent::class)
12+
object TimedSheetModule {
13+
14+
@Provides
15+
fun provideTimedSheetManagerProvider(): (CoroutineScope) -> TimedSheetManager {
16+
return ::TimedSheetManager
17+
}
18+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package to.bitkit.utils.timedsheets
2+
3+
import to.bitkit.ui.components.TimedSheetType
4+
5+
interface TimedSheetItem {
6+
val type: TimedSheetType
7+
val priority: Int
8+
9+
suspend fun shouldShow(): Boolean
10+
suspend fun onShown()
11+
suspend fun onDismissed()
12+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package to.bitkit.utils.timedsheets
2+
3+
import kotlinx.coroutines.CoroutineScope
4+
import kotlinx.coroutines.Job
5+
import kotlinx.coroutines.delay
6+
import kotlinx.coroutines.flow.MutableStateFlow
7+
import kotlinx.coroutines.flow.StateFlow
8+
import kotlinx.coroutines.flow.asStateFlow
9+
import kotlinx.coroutines.launch
10+
import to.bitkit.ui.components.TimedSheetType
11+
import to.bitkit.utils.Logger
12+
13+
class TimedSheetManager(private val scope: CoroutineScope) {
14+
private val _currentSheet = MutableStateFlow<TimedSheetType?>(null)
15+
val currentSheet: StateFlow<TimedSheetType?> = _currentSheet.asStateFlow()
16+
17+
private val registeredSheets = mutableListOf<TimedSheetItem>()
18+
private var currentTimedSheet: TimedSheetItem? = null
19+
private var checkJob: Job? = null
20+
21+
fun registerSheet(sheet: TimedSheetItem) {
22+
registeredSheets.add(sheet)
23+
registeredSheets.sortByDescending { it.priority }
24+
Logger.debug(
25+
"Registered timed sheet: ${sheet.type.name} with priority: ${sheet.priority}",
26+
context = TAG
27+
)
28+
}
29+
30+
fun onHomeScreenEntered() {
31+
Logger.debug("User entered home screen, starting timer", context = TAG)
32+
checkJob?.cancel()
33+
checkJob = scope.launch {
34+
delay(CHECK_DELAY_MILLIS)
35+
checkAndShowNextSheet()
36+
}
37+
}
38+
39+
fun onHomeScreenExited() {
40+
Logger.debug("User exited home screen, cancelling timer", context = TAG)
41+
checkJob?.cancel()
42+
checkJob = null
43+
}
44+
45+
fun dismissCurrentSheet(skipQueue: Boolean = false) {
46+
scope.launch {
47+
currentTimedSheet?.onDismissed()
48+
49+
if (skipQueue) {
50+
Logger.debug("Clearing timed sheet queue", context = TAG)
51+
_currentSheet.value = null
52+
currentTimedSheet = null
53+
} else {
54+
checkAndShowNextSheet()
55+
}
56+
}
57+
}
58+
59+
private suspend fun checkAndShowNextSheet() {
60+
for (sheet in registeredSheets.toList()) {
61+
if (sheet.shouldShow()) {
62+
Logger.debug(
63+
"Showing timed sheet: ${sheet.type.name} with priority: ${sheet.priority}",
64+
context = TAG
65+
)
66+
currentTimedSheet = sheet
67+
_currentSheet.value = sheet.type
68+
sheet.onShown()
69+
registeredSheets.remove(sheet)
70+
return
71+
}
72+
}
73+
74+
Logger.debug("No timed sheets need to be shown", context = TAG)
75+
_currentSheet.value = null
76+
currentTimedSheet = null
77+
}
78+
79+
companion object {
80+
private const val TAG = "TimedSheetManager"
81+
private const val CHECK_DELAY_MILLIS = 2000L
82+
}
83+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package to.bitkit.utils.timedsheets
2+
3+
import kotlin.time.Clock
4+
import kotlin.time.ExperimentalTime
5+
6+
@OptIn(ExperimentalTime::class)
7+
fun checkTimeout(
8+
lastIgnoredMillis: Long,
9+
intervalMillis: Long,
10+
additionalCondition: Boolean = true,
11+
): Boolean {
12+
if (!additionalCondition) return false
13+
14+
val currentTime = Clock.System.now().toEpochMilliseconds()
15+
val isTimeOutOver = lastIgnoredMillis == 0L ||
16+
(currentTime - lastIgnoredMillis > intervalMillis)
17+
return isTimeOutOver
18+
}
19+
20+
const val ONE_DAY_ASK_INTERVAL_MILLIS = 1000 * 60 * 60 * 24L
21+
const val ONE_WEEK_ASK_INTERVAL_MILLIS = ONE_DAY_ASK_INTERVAL_MILLIS * 7L
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package to.bitkit.utils.timedsheets.sheets
2+
3+
import kotlinx.coroutines.CoroutineDispatcher
4+
import kotlinx.coroutines.withContext
5+
import to.bitkit.BuildConfig
6+
import to.bitkit.di.BgDispatcher
7+
import to.bitkit.services.AppUpdaterService
8+
import to.bitkit.ui.components.TimedSheetType
9+
import to.bitkit.utils.Logger
10+
import to.bitkit.utils.timedsheets.TimedSheetItem
11+
import javax.inject.Inject
12+
13+
class AppUpdateTimedSheet @Inject constructor(
14+
private val appUpdaterService: AppUpdaterService,
15+
@BgDispatcher private val bgDispatcher: CoroutineDispatcher,
16+
) : TimedSheetItem {
17+
override val type = TimedSheetType.APP_UPDATE
18+
override val priority = 5
19+
20+
override suspend fun shouldShow(): Boolean = withContext(bgDispatcher) {
21+
try {
22+
val androidReleaseInfo = appUpdaterService.getReleaseInfo().platforms.android
23+
val currentBuildNumber = BuildConfig.VERSION_CODE
24+
25+
if (androidReleaseInfo.buildNumber <= currentBuildNumber) return@withContext false
26+
27+
if (androidReleaseInfo.isCritical) {
28+
return@withContext false
29+
}
30+
31+
return@withContext true
32+
} catch (e: Exception) {
33+
Logger.warn("Failure fetching new releases", e = e, context = TAG)
34+
return@withContext false
35+
}
36+
}
37+
38+
override suspend fun onShown() {
39+
Logger.debug("App update sheet shown", context = TAG)
40+
}
41+
42+
override suspend fun onDismissed() {
43+
Logger.debug("App update sheet dismissed", context = TAG)
44+
}
45+
46+
companion object {
47+
private const val TAG = "AppUpdateTimedSheet"
48+
}
49+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package to.bitkit.utils.timedsheets.sheets
2+
3+
import kotlinx.coroutines.flow.first
4+
import to.bitkit.data.SettingsStore
5+
import to.bitkit.ext.nowMillis
6+
import to.bitkit.repositories.WalletRepo
7+
import to.bitkit.ui.components.TimedSheetType
8+
import to.bitkit.utils.Logger
9+
import to.bitkit.utils.timedsheets.ONE_DAY_ASK_INTERVAL_MILLIS
10+
import to.bitkit.utils.timedsheets.TimedSheetItem
11+
import to.bitkit.utils.timedsheets.checkTimeout
12+
import javax.inject.Inject
13+
import kotlin.time.ExperimentalTime
14+
15+
class BackupTimedSheet @Inject constructor(
16+
private val settingsStore: SettingsStore,
17+
private val walletRepo: WalletRepo,
18+
) : TimedSheetItem {
19+
override val type = TimedSheetType.BACKUP
20+
override val priority = 4
21+
22+
override suspend fun shouldShow(): Boolean {
23+
val settings = settingsStore.data.first()
24+
if (settings.backupVerified) return false
25+
26+
val hasBalance = walletRepo.balanceState.value.totalSats > 0U
27+
if (!hasBalance) return false
28+
29+
return checkTimeout(
30+
lastIgnoredMillis = settings.backupWarningIgnoredMillis,
31+
intervalMillis = ONE_DAY_ASK_INTERVAL_MILLIS
32+
)
33+
}
34+
35+
override suspend fun onShown() {
36+
Logger.debug("Backup sheet shown", context = TAG)
37+
}
38+
39+
@OptIn(ExperimentalTime::class)
40+
override suspend fun onDismissed() {
41+
val currentTime = nowMillis()
42+
settingsStore.update {
43+
it.copy(backupWarningIgnoredMillis = currentTime)
44+
}
45+
Logger.debug("Backup sheet dismissed", context = TAG)
46+
}
47+
48+
companion object {
49+
private const val TAG = "BackupTimedSheet"
50+
}
51+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package to.bitkit.utils.timedsheets.sheets
2+
3+
import kotlinx.coroutines.flow.first
4+
import to.bitkit.data.SettingsStore
5+
import to.bitkit.ext.nowMillis
6+
import to.bitkit.repositories.CurrencyRepo
7+
import to.bitkit.repositories.WalletRepo
8+
import to.bitkit.ui.components.TimedSheetType
9+
import to.bitkit.utils.Logger
10+
import to.bitkit.utils.timedsheets.ONE_DAY_ASK_INTERVAL_MILLIS
11+
import to.bitkit.utils.timedsheets.TimedSheetItem
12+
import to.bitkit.utils.timedsheets.checkTimeout
13+
import java.math.BigDecimal
14+
import javax.inject.Inject
15+
import kotlin.time.ExperimentalTime
16+
17+
class HighBalanceTimedSheet @Inject constructor(
18+
private val settingsStore: SettingsStore,
19+
private val walletRepo: WalletRepo,
20+
private val currencyRepo: CurrencyRepo,
21+
) : TimedSheetItem {
22+
override val type = TimedSheetType.HIGH_BALANCE
23+
override val priority = 1
24+
25+
override suspend fun shouldShow(): Boolean {
26+
val settings = settingsStore.data.first()
27+
28+
val totalOnChainSats = walletRepo.balanceState.value.totalSats
29+
val balanceUsd = satsToUsd(totalOnChainSats) ?: return false
30+
val thresholdReached = balanceUsd > BigDecimal(BALANCE_THRESHOLD_USD)
31+
32+
if (!thresholdReached) {
33+
settingsStore.update { it.copy(balanceWarningTimes = 0) }
34+
return false
35+
}
36+
37+
val belowMaxWarnings = settings.balanceWarningTimes < MAX_WARNINGS
38+
39+
return checkTimeout(
40+
lastIgnoredMillis = settings.balanceWarningIgnoredMillis,
41+
intervalMillis = ONE_DAY_ASK_INTERVAL_MILLIS,
42+
additionalCondition = belowMaxWarnings
43+
)
44+
}
45+
46+
override suspend fun onShown() {
47+
Logger.debug("High balance sheet shown", context = TAG)
48+
}
49+
50+
@OptIn(ExperimentalTime::class)
51+
override suspend fun onDismissed() {
52+
val currentTime = nowMillis()
53+
settingsStore.update {
54+
it.copy(
55+
balanceWarningTimes = it.balanceWarningTimes + 1,
56+
balanceWarningIgnoredMillis = currentTime,
57+
)
58+
}
59+
Logger.debug("High balance sheet dismissed", context = TAG)
60+
}
61+
62+
private fun satsToUsd(sats: ULong): BigDecimal? {
63+
val converted = currencyRepo.convertSatsToFiat(sats = sats.toLong(), currency = "USD").getOrNull()
64+
return converted?.value
65+
}
66+
67+
companion object {
68+
private const val TAG = "HighBalanceTimedSheet"
69+
private const val BALANCE_THRESHOLD_USD = 500L
70+
private const val MAX_WARNINGS = 3
71+
}
72+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package to.bitkit.utils.timedsheets.sheets
2+
3+
import kotlinx.coroutines.flow.first
4+
import to.bitkit.data.SettingsStore
5+
import to.bitkit.ext.nowMillis
6+
import to.bitkit.repositories.WalletRepo
7+
import to.bitkit.ui.components.TimedSheetType
8+
import to.bitkit.utils.Logger
9+
import to.bitkit.utils.timedsheets.ONE_WEEK_ASK_INTERVAL_MILLIS
10+
import to.bitkit.utils.timedsheets.TimedSheetItem
11+
import to.bitkit.utils.timedsheets.checkTimeout
12+
import javax.inject.Inject
13+
import kotlin.time.ExperimentalTime
14+
15+
class NotificationsTimedSheet @Inject constructor(
16+
private val settingsStore: SettingsStore,
17+
private val walletRepo: WalletRepo,
18+
) : TimedSheetItem {
19+
override val type = TimedSheetType.NOTIFICATIONS
20+
override val priority = 3
21+
22+
override suspend fun shouldShow(): Boolean {
23+
val settings = settingsStore.data.first()
24+
if (settings.notificationsGranted) return false
25+
if (walletRepo.balanceState.value.totalLightningSats == 0UL) return false
26+
27+
return checkTimeout(
28+
lastIgnoredMillis = settings.notificationsIgnoredMillis,
29+
intervalMillis = ONE_WEEK_ASK_INTERVAL_MILLIS
30+
)
31+
}
32+
33+
override suspend fun onShown() {
34+
Logger.debug("Notifications sheet shown", context = TAG)
35+
}
36+
37+
@OptIn(ExperimentalTime::class)
38+
override suspend fun onDismissed() {
39+
val currentTime = nowMillis()
40+
settingsStore.update {
41+
it.copy(notificationsIgnoredMillis = currentTime)
42+
}
43+
Logger.debug("Notifications sheet dismissed", context = TAG)
44+
}
45+
46+
companion object {
47+
private const val TAG = "NotificationsTimedSheet"
48+
}
49+
}

0 commit comments

Comments
 (0)