@@ -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
@@ -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)
0 commit comments