Skip to content

Commit 9ea46ef

Browse files
authored
Merge branch 'master' into fix/node-stopping-bg-payments
2 parents 3a94b38 + 3bc7c3d commit 9ea46ef

File tree

13 files changed

+235
-104
lines changed

13 files changed

+235
-104
lines changed

app/src/main/java/to/bitkit/repositories/WidgetsRepo.kt

Lines changed: 88 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@ package to.bitkit.repositories
22

33
import kotlinx.coroutines.CoroutineDispatcher
44
import kotlinx.coroutines.CoroutineScope
5+
import kotlinx.coroutines.Job
56
import kotlinx.coroutines.SupervisorJob
67
import kotlinx.coroutines.delay
78
import kotlinx.coroutines.flow.MutableStateFlow
89
import kotlinx.coroutines.flow.StateFlow
910
import kotlinx.coroutines.flow.asStateFlow
11+
import kotlinx.coroutines.flow.distinctUntilChanged
1012
import kotlinx.coroutines.flow.first
1113
import kotlinx.coroutines.flow.map
1214
import kotlinx.coroutines.flow.update
15+
import kotlinx.coroutines.isActive
1316
import kotlinx.coroutines.launch
1417
import kotlinx.coroutines.withContext
1518
import to.bitkit.data.SettingsStore
@@ -30,6 +33,7 @@ import to.bitkit.models.widget.HeadlinePreferences
3033
import to.bitkit.models.widget.PricePreferences
3134
import to.bitkit.models.widget.WeatherPreferences
3235
import to.bitkit.utils.Logger
36+
import java.util.concurrent.ConcurrentHashMap
3337
import javax.inject.Inject
3438
import javax.inject.Singleton
3539

@@ -45,9 +49,8 @@ class WidgetsRepo @Inject constructor(
4549
private val widgetsStore: WidgetsStore,
4650
private val settingsStore: SettingsStore,
4751
) {
48-
// TODO Only refresh in loop widgets displayed in the Home
49-
// TODO Perform a refresh when the preview screen is displayed
5052
private val repoScope = CoroutineScope(bgDispatcher + SupervisorJob())
53+
private val widgetJobs = ConcurrentHashMap<WidgetType, Job>()
5154

5255
val widgetsDataFlow = widgetsStore.data
5356
val showWidgetTitles = settingsStore.data.map { it.showWidgetTitles }
@@ -65,7 +68,86 @@ class WidgetsRepo @Inject constructor(
6568
val refreshStates: StateFlow<Map<WidgetType, Boolean>> = _refreshStates.asStateFlow()
6669

6770
init {
68-
startPeriodicUpdates()
71+
observeWidgetStateChanges()
72+
}
73+
74+
private fun observeWidgetStateChanges() {
75+
repoScope.launch {
76+
widgetsDataFlow
77+
.map { it.widgets.map { widget -> widget.type }.toSet() }
78+
.distinctUntilChanged()
79+
.collect { enabledWidgetTypes ->
80+
updateWidgetJobs(enabledWidgetTypes)
81+
}
82+
}
83+
}
84+
85+
private fun updateWidgetJobs(enabledWidgetTypes: Set<WidgetType>) {
86+
val widgetTypesWithServices = WidgetType.entries.filter {
87+
it != WidgetType.CALCULATOR
88+
}
89+
90+
widgetTypesWithServices.forEach { widgetType ->
91+
val isEnabled = widgetType in enabledWidgetTypes
92+
val hasRunningJob = widgetJobs.containsKey(widgetType) &&
93+
widgetJobs[widgetType]?.isActive == true
94+
95+
when {
96+
isEnabled && !hasRunningJob -> startWidgetRefresh(widgetType)
97+
!isEnabled && hasRunningJob -> stopWidgetRefresh(widgetType)
98+
}
99+
}
100+
}
101+
102+
private fun startWidgetRefresh(widgetType: WidgetType) {
103+
stopWidgetRefresh(widgetType)
104+
105+
val job = when (widgetType) {
106+
WidgetType.NEWS -> repoScope.launch {
107+
while (isActive) {
108+
updateWidget(newsService) { widgetsStore.updateArticles(it) }
109+
delay(newsService.refreshInterval)
110+
}
111+
}
112+
113+
WidgetType.FACTS -> repoScope.launch {
114+
while (isActive) {
115+
updateWidget(factsService) { widgetsStore.updateFacts(it) }
116+
delay(factsService.refreshInterval)
117+
}
118+
}
119+
120+
WidgetType.BLOCK -> repoScope.launch {
121+
while (isActive) {
122+
updateWidget(blocksService) { widgetsStore.updateBlock(it) }
123+
delay(blocksService.refreshInterval)
124+
}
125+
}
126+
127+
WidgetType.WEATHER -> repoScope.launch {
128+
while (isActive) {
129+
updateWidget(weatherService) { widgetsStore.updateWeather(it) }
130+
delay(weatherService.refreshInterval)
131+
}
132+
}
133+
134+
WidgetType.PRICE -> repoScope.launch {
135+
while (isActive) {
136+
updateWidget(priceService) { widgetsStore.updatePrice(it) }
137+
delay(priceService.refreshInterval)
138+
}
139+
}
140+
141+
WidgetType.CALCULATOR -> throw NotImplementedError("Calculator widget doesn't need a service")
142+
}
143+
144+
widgetJobs[widgetType] = job
145+
}
146+
147+
private fun stopWidgetRefresh(widgetType: WidgetType) {
148+
widgetJobs[widgetType]?.cancel()
149+
widgetJobs.remove(widgetType)
150+
Logger.verbose("Stopped refresh coroutine for $widgetType", context = TAG)
69151
}
70152

71153
suspend fun addWidget(type: WidgetType) = withContext(bgDispatcher) { widgetsStore.addWidget(type) }
@@ -98,85 +180,25 @@ class WidgetsRepo @Inject constructor(
98180

99181
suspend fun fetchAllPeriods() = withContext(bgDispatcher) { priceService.fetchAllPeriods() }
100182

101-
/**
102-
* Start periodic updates for all widgets
103-
*/
104-
private fun startPeriodicUpdates() {
105-
startPeriodicUpdate(newsService) { articles ->
106-
widgetsStore.updateArticles(articles)
107-
}
108-
startPeriodicUpdate(factsService) { facts ->
109-
widgetsStore.updateFacts(facts)
110-
}
111-
startPeriodicUpdate(blocksService) { block ->
112-
widgetsStore.updateBlock(block)
113-
}
114-
startPeriodicUpdate(weatherService) { weather ->
115-
widgetsStore.updateWeather(weather)
116-
}
117-
startPeriodicUpdate(priceService) { price ->
118-
widgetsStore.updatePrice(price)
119-
}
120-
}
121-
122-
/**
123-
* Generic method to start periodic updates for any widget service
124-
*/
125-
private fun <T> startPeriodicUpdate(
126-
service: WidgetService<T>,
127-
updateStore: suspend (T) -> Unit
128-
) {
129-
repoScope.launch {
130-
while (true) {
131-
updateWidget(service, updateStore)
132-
delay(service.refreshInterval)
133-
}
134-
}
135-
}
136-
137-
/**
138-
* Update a specific widget type
139-
*/
140183
private suspend fun <T> updateWidget(
141184
service: WidgetService<T>,
142-
updateStore: suspend (T) -> Unit
185+
updateStore: suspend (T) -> Unit,
143186
) {
144187
val widgetType = service.widgetType
145188
_refreshStates.update { it + (widgetType to true) }
146189

147190
service.fetchData()
148191
.onSuccess { data ->
149192
updateStore(data)
150-
Logger.verbose("Updated $widgetType widget successfully")
193+
Logger.verbose("Updated $widgetType widget successfully", context = TAG)
151194
}
152195
.onFailure { e ->
153-
Logger.verbose("Failed to update $widgetType widget", e = e, context = TAG)
196+
Logger.verbose("Failed to update $widgetType widget", e, context = TAG)
154197
}
155198

156199
_refreshStates.update { it + (widgetType to false) }
157200
}
158201

159-
/**
160-
* Manually refresh all widgets
161-
*/
162-
suspend fun refreshAllWidgets(): Result<Unit> = runCatching {
163-
updateWidget(newsService) { articles ->
164-
widgetsStore.updateArticles(articles)
165-
}
166-
updateWidget(factsService) { facts ->
167-
widgetsStore.updateFacts(facts)
168-
}
169-
updateWidget(blocksService) { block ->
170-
widgetsStore.updateBlock(block)
171-
}
172-
updateWidget(weatherService) { weather ->
173-
widgetsStore.updateWeather(weather)
174-
}
175-
updateWidget(priceService) { price ->
176-
widgetsStore.updatePrice(price)
177-
}
178-
}
179-
180202
suspend fun refreshEnabledWidgets() = withContext(bgDispatcher) {
181203
widgetsDataFlow.first().widgets.forEach {
182204
refreshWidget(it.type)

app/src/main/java/to/bitkit/ui/screens/widgets/blocks/BlocksPreviewScreen.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.width
1212
import androidx.compose.material3.HorizontalDivider
1313
import androidx.compose.material3.Icon
1414
import androidx.compose.runtime.Composable
15+
import androidx.compose.runtime.LaunchedEffect
1516
import androidx.compose.runtime.getValue
1617
import androidx.compose.ui.Alignment
1718
import androidx.compose.ui.Modifier
@@ -51,6 +52,10 @@ fun BlocksPreviewScreen(
5152
val currentBlock by blocksViewModel.currentBlock.collectAsStateWithLifecycle()
5253
val isBlocksWidgetEnabled by blocksViewModel.isBlocksWidgetEnabled.collectAsStateWithLifecycle()
5354

55+
LaunchedEffect(Unit) {
56+
blocksViewModel.refreshOnDisplay()
57+
}
58+
5459
BlocksPreviewContent(
5560
onBack = onBack,
5661
isBlocksWidgetEnabled = isBlocksWidgetEnabled,

app/src/main/java/to/bitkit/ui/screens/widgets/blocks/BlocksViewModel.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,12 @@ class BlocksViewModel @Inject constructor(
122122
}
123123
}
124124

125+
fun refreshOnDisplay() {
126+
viewModelScope.launch {
127+
widgetsRepo.refreshWidget(WidgetType.BLOCK)
128+
}
129+
}
130+
125131
// MARK: - Private Methods
126132

127133
private fun initializeCustomPreferences() {

app/src/main/java/to/bitkit/ui/screens/widgets/facts/FactsPreviewScreen.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.width
1212
import androidx.compose.material3.HorizontalDivider
1313
import androidx.compose.material3.Icon
1414
import androidx.compose.runtime.Composable
15+
import androidx.compose.runtime.LaunchedEffect
1516
import androidx.compose.runtime.getValue
1617
import androidx.compose.ui.Alignment
1718
import androidx.compose.ui.Modifier
@@ -50,6 +51,10 @@ fun FactsPreviewScreen(
5051
val fact by factsViewModel.currentFact.collectAsStateWithLifecycle()
5152
val isFactsWidgetEnabled by factsViewModel.isFactsWidgetEnabled.collectAsStateWithLifecycle()
5253

54+
LaunchedEffect(Unit) {
55+
factsViewModel.refreshOnDisplay()
56+
}
57+
5358
FactsPreviewContent(
5459
onBack = onBack,
5560
isFactsWidgetEnabled = isFactsWidgetEnabled,

app/src/main/java/to/bitkit/ui/screens/widgets/facts/FactsViewModel.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,12 @@ class FactsViewModel @Inject constructor(
8989
}
9090
}
9191

92+
fun refreshOnDisplay() {
93+
viewModelScope.launch {
94+
widgetsRepo.refreshWidget(WidgetType.FACTS)
95+
}
96+
}
97+
9298
// MARK: - Private Methods
9399

94100
private fun initializeCustomPreferences() {

app/src/main/java/to/bitkit/ui/screens/widgets/headlines/HeadlinesPreviewScreen.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.width
1212
import androidx.compose.material3.HorizontalDivider
1313
import androidx.compose.material3.Icon
1414
import androidx.compose.runtime.Composable
15+
import androidx.compose.runtime.LaunchedEffect
1516
import androidx.compose.runtime.getValue
1617
import androidx.compose.ui.Alignment
1718
import androidx.compose.ui.Modifier
@@ -51,6 +52,10 @@ fun HeadlinesPreviewScreen(
5152
val article by headlinesViewModel.currentArticle.collectAsStateWithLifecycle()
5253
val isHeadlinesImplemented by headlinesViewModel.isNewsWidgetEnabled.collectAsStateWithLifecycle()
5354

55+
LaunchedEffect(Unit) {
56+
headlinesViewModel.refreshOnDisplay()
57+
}
58+
5459
HeadlinesPreviewContent(
5560
onBack = onBack,
5661
isHeadlinesImplemented = isHeadlinesImplemented,

app/src/main/java/to/bitkit/ui/screens/widgets/headlines/HeadlinesViewModel.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,12 @@ class HeadlinesViewModel @Inject constructor(
9898
}
9999
}
100100

101+
fun refreshOnDisplay() {
102+
viewModelScope.launch {
103+
widgetsRepo.refreshWidget(WidgetType.NEWS)
104+
}
105+
}
106+
101107
// MARK: - Private Methods
102108

103109
private fun initializeCustomPreferences() {

app/src/main/java/to/bitkit/ui/screens/widgets/price/PricePreviewScreen.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ fun PricePreviewScreen(
6060
val isPriceWidgetEnabled by priceViewModel.isPriceWidgetEnabled.collectAsStateWithLifecycle()
6161
val isLoading by priceViewModel.isLoading.collectAsStateWithLifecycle()
6262

63+
LaunchedEffect(Unit) {
64+
priceViewModel.refreshOnDisplay()
65+
}
66+
6367
LaunchedEffect(Unit) {
6468
priceViewModel.priceEffect.collect { effect ->
6569
when (effect) {

app/src/main/java/to/bitkit/ui/screens/widgets/price/PriceViewModel.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,12 @@ class PriceViewModel @Inject constructor(
130130
}
131131
}
132132

133+
fun refreshOnDisplay() {
134+
viewModelScope.launch {
135+
widgetsRepo.refreshWidget(WidgetType.PRICE)
136+
}
137+
}
138+
133139
private fun initializeCustomPreferences() {
134140
viewModelScope.launch {
135141
pricePreferences.collect { preferences ->

app/src/main/java/to/bitkit/ui/screens/widgets/weather/WeatherPreviewScreen.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import androidx.compose.foundation.verticalScroll
1414
import androidx.compose.material3.HorizontalDivider
1515
import androidx.compose.material3.Icon
1616
import androidx.compose.runtime.Composable
17+
import androidx.compose.runtime.LaunchedEffect
1718
import androidx.compose.runtime.getValue
1819
import androidx.compose.ui.Alignment
1920
import androidx.compose.ui.Modifier
@@ -53,6 +54,10 @@ fun WeatherPreviewScreen(
5354
val weather by weatherViewModel.currentWeather.collectAsStateWithLifecycle()
5455
val isWeatherWidgetEnabled by weatherViewModel.isWeatherWidgetEnabled.collectAsStateWithLifecycle()
5556

57+
LaunchedEffect(Unit) {
58+
weatherViewModel.refreshOnDisplay()
59+
}
60+
5661
WeatherPreviewContent(
5762
onBack = onBack,
5863
isWeatherWidgetEnabled = isWeatherWidgetEnabled,

0 commit comments

Comments
 (0)