Skip to content

Commit 4df76af

Browse files
authored
Merge pull request #560 from synonymdev/fix/skip-autorefresh-for-disabled-widgets
fix: skip auto refresh for disabled widgets
2 parents cbc152e + c2e62e9 commit 4df76af

File tree

11 files changed

+141
-64
lines changed

11 files changed

+141
-64
lines changed

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

Lines changed: 87 additions & 64 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

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

5154
val widgetsDataFlow = widgetsStore.data
5255
val showWidgetTitles = settingsStore.data.map { it.showWidgetTitles }
@@ -63,7 +66,86 @@ class WidgetsRepo @Inject constructor(
6366
val refreshStates: StateFlow<Map<WidgetType, Boolean>> = _refreshStates.asStateFlow()
6467

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

69151
suspend fun addWidget(type: WidgetType) = withContext(bgDispatcher) { widgetsStore.addWidget(type) }
@@ -96,56 +178,18 @@ class WidgetsRepo @Inject constructor(
96178

97179
suspend fun fetchAllPeriods() = withContext(bgDispatcher) { priceService.fetchAllPeriods() }
98180

99-
/**
100-
* Start periodic updates for all widgets
101-
*/
102-
private fun startPeriodicUpdates() {
103-
startPeriodicUpdate(newsService) { articles ->
104-
widgetsStore.updateArticles(articles)
105-
}
106-
startPeriodicUpdate(factsService) { facts ->
107-
widgetsStore.updateFacts(facts)
108-
}
109-
startPeriodicUpdate(blocksService) { block ->
110-
widgetsStore.updateBlock(block)
111-
}
112-
startPeriodicUpdate(weatherService) { weather ->
113-
widgetsStore.updateWeather(weather)
114-
}
115-
startPeriodicUpdate(priceService) { price ->
116-
widgetsStore.updatePrice(price)
117-
}
118-
}
119-
120-
/**
121-
* Generic method to start periodic updates for any widget service
122-
*/
123-
private fun <T> startPeriodicUpdate(
124-
service: WidgetService<T>,
125-
updateStore: suspend (T) -> Unit
126-
) {
127-
repoScope.launch {
128-
while (true) {
129-
updateWidget(service, updateStore)
130-
delay(service.refreshInterval)
131-
}
132-
}
133-
}
134181

135-
/**
136-
* Update a specific widget type
137-
*/
138182
private suspend fun <T> updateWidget(
139183
service: WidgetService<T>,
140-
updateStore: suspend (T) -> Unit
184+
updateStore: suspend (T) -> Unit,
141185
) {
142186
val widgetType = service.widgetType
143187
_refreshStates.update { it + (widgetType to true) }
144188

145189
service.fetchData()
146190
.onSuccess { data ->
147191
updateStore(data)
148-
Logger.verbose("Updated $widgetType widget successfully")
192+
Logger.verbose("Updated $widgetType widget successfully", context = TAG)
149193
}
150194
.onFailure { e ->
151195
Logger.verbose("Failed to update $widgetType widget", e = e, context = TAG)
@@ -154,27 +198,6 @@ class WidgetsRepo @Inject constructor(
154198
_refreshStates.update { it + (widgetType to false) }
155199
}
156200

157-
/**
158-
* Manually refresh all widgets
159-
*/
160-
suspend fun refreshAllWidgets(): Result<Unit> = runCatching {
161-
updateWidget(newsService) { articles ->
162-
widgetsStore.updateArticles(articles)
163-
}
164-
updateWidget(factsService) { facts ->
165-
widgetsStore.updateFacts(facts)
166-
}
167-
updateWidget(blocksService) { block ->
168-
widgetsStore.updateBlock(block)
169-
}
170-
updateWidget(weatherService) { weather ->
171-
widgetsStore.updateWeather(weather)
172-
}
173-
updateWidget(priceService) { price ->
174-
widgetsStore.updatePrice(price)
175-
}
176-
}
177-
178201
suspend fun refreshEnabledWidgets() = withContext(bgDispatcher) {
179202
widgetsDataFlow.first().widgets.forEach {
180203
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)