@@ -2,14 +2,17 @@ package to.bitkit.repositories
22
33import kotlinx.coroutines.CoroutineDispatcher
44import kotlinx.coroutines.CoroutineScope
5+ import kotlinx.coroutines.Job
56import kotlinx.coroutines.SupervisorJob
67import kotlinx.coroutines.delay
78import kotlinx.coroutines.flow.MutableStateFlow
89import kotlinx.coroutines.flow.StateFlow
910import kotlinx.coroutines.flow.asStateFlow
11+ import kotlinx.coroutines.flow.distinctUntilChanged
1012import kotlinx.coroutines.flow.first
1113import kotlinx.coroutines.flow.map
1214import kotlinx.coroutines.flow.update
15+ import kotlinx.coroutines.isActive
1316import kotlinx.coroutines.launch
1417import kotlinx.coroutines.withContext
1518import to.bitkit.data.SettingsStore
@@ -30,6 +33,7 @@ import to.bitkit.models.widget.HeadlinePreferences
3033import to.bitkit.models.widget.PricePreferences
3134import to.bitkit.models.widget.WeatherPreferences
3235import to.bitkit.utils.Logger
36+ import java.util.concurrent.ConcurrentHashMap
3337import javax.inject.Inject
3438import 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)
0 commit comments