@@ -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,6 +49,7 @@ class WidgetsRepo @Inject constructor(
4549 private val settingsStore : SettingsStore ,
4650) {
4751 private val repoScope = CoroutineScope (bgDispatcher + SupervisorJob ())
52+ private val widgetJobs = ConcurrentHashMap <WidgetType , Job >()
4853
4954 val widgetsDataFlow = widgetsStore.data
5055 val showWidgetTitles = settingsStore.data.map { it.showWidgetTitles }
@@ -61,7 +66,99 @@ class WidgetsRepo @Inject constructor(
6166 val refreshStates: StateFlow <Map <WidgetType , Boolean >> = _refreshStates .asStateFlow()
6267
6368 init {
64- startPeriodicUpdates()
69+ observeWidgetStateChanges()
70+ }
71+
72+ /* *
73+ * Observe widget enable/disable changes and manage coroutine lifecycle
74+ */
75+ private fun observeWidgetStateChanges () {
76+ repoScope.launch {
77+ widgetsDataFlow
78+ .map { it.widgets.map { widget -> widget.type }.toSet() }
79+ .distinctUntilChanged()
80+ .collect { enabledWidgetTypes ->
81+ updateWidgetJobs(enabledWidgetTypes)
82+ }
83+ }
84+ }
85+
86+ /* *
87+ * Sync running jobs with enabled widgets
88+ * - Cancel jobs for disabled widgets
89+ * - Start jobs for newly enabled widgets
90+ */
91+ private fun updateWidgetJobs (enabledWidgetTypes : Set <WidgetType >) {
92+ val widgetTypesWithServices = WidgetType .entries.filter {
93+ it != WidgetType .CALCULATOR
94+ }
95+
96+ widgetTypesWithServices.forEach { widgetType ->
97+ val isEnabled = widgetType in enabledWidgetTypes
98+ val hasRunningJob = widgetJobs.containsKey(widgetType) &&
99+ widgetJobs[widgetType]?.isActive == true
100+
101+ when {
102+ isEnabled && ! hasRunningJob -> startWidgetRefresh(widgetType)
103+ ! isEnabled && hasRunningJob -> stopWidgetRefresh(widgetType)
104+ }
105+ }
106+ }
107+
108+ /* *
109+ * Start periodic refresh for a specific widget type
110+ */
111+ private fun startWidgetRefresh (widgetType : WidgetType ) {
112+ stopWidgetRefresh(widgetType)
113+
114+ val job = when (widgetType) {
115+ WidgetType .NEWS -> repoScope.launch {
116+ while (isActive) {
117+ updateWidget(newsService) { widgetsStore.updateArticles(it) }
118+ delay(newsService.refreshInterval)
119+ }
120+ }
121+
122+ WidgetType .FACTS -> repoScope.launch {
123+ while (isActive) {
124+ updateWidget(factsService) { widgetsStore.updateFacts(it) }
125+ delay(factsService.refreshInterval)
126+ }
127+ }
128+
129+ WidgetType .BLOCK -> repoScope.launch {
130+ while (isActive) {
131+ updateWidget(blocksService) { widgetsStore.updateBlock(it) }
132+ delay(blocksService.refreshInterval)
133+ }
134+ }
135+
136+ WidgetType .WEATHER -> repoScope.launch {
137+ while (isActive) {
138+ updateWidget(weatherService) { widgetsStore.updateWeather(it) }
139+ delay(weatherService.refreshInterval)
140+ }
141+ }
142+
143+ WidgetType .PRICE -> repoScope.launch {
144+ while (isActive) {
145+ updateWidget(priceService) { widgetsStore.updatePrice(it) }
146+ delay(priceService.refreshInterval)
147+ }
148+ }
149+
150+ WidgetType .CALCULATOR -> throw NotImplementedError (" Calculator widget doesn't need a service" )
151+ }
152+
153+ widgetJobs[widgetType] = job
154+ }
155+
156+ /* *
157+ * Stop refresh coroutine for a specific widget type
158+ */
159+ private fun stopWidgetRefresh (widgetType : WidgetType ) {
160+ widgetJobs[widgetType]?.cancel()
161+ widgetJobs.remove(widgetType)
65162 }
66163
67164 suspend fun addWidget (type : WidgetType ) = withContext(bgDispatcher) { widgetsStore.addWidget(type) }
@@ -94,55 +191,12 @@ class WidgetsRepo @Inject constructor(
94191
95192 suspend fun fetchAllPeriods () = withContext(bgDispatcher) { priceService.fetchAllPeriods() }
96193
97- /* *
98- * Start periodic updates for all widgets
99- */
100- private fun startPeriodicUpdates () {
101- startPeriodicUpdate(newsService) { articles ->
102- widgetsStore.updateArticles(articles)
103- }
104- startPeriodicUpdate(factsService) { facts ->
105- widgetsStore.updateFacts(facts)
106- }
107- startPeriodicUpdate(blocksService) { block ->
108- widgetsStore.updateBlock(block)
109- }
110- startPeriodicUpdate(weatherService) { weather ->
111- widgetsStore.updateWeather(weather)
112- }
113- startPeriodicUpdate(priceService) { price ->
114- widgetsStore.updatePrice(price)
115- }
116- }
117-
118- /* *
119- * Generic method to start periodic updates for any widget service
120- */
121- private fun <T > startPeriodicUpdate (
122- service : WidgetService <T >,
123- updateStore : suspend (T ) -> Unit
124- ) {
125- repoScope.launch {
126- while (true ) {
127- val isEnabled = widgetsDataFlow.first().widgets.any {
128- it.type == service.widgetType
129- }
130-
131- if (isEnabled) {
132- updateWidget(service, updateStore)
133- }
134-
135- delay(service.refreshInterval)
136- }
137- }
138- }
139-
140194 /* *
141195 * Update a specific widget type
142196 */
143197 private suspend fun <T > updateWidget (
144198 service : WidgetService <T >,
145- updateStore : suspend (T ) -> Unit
199+ updateStore : suspend (T ) -> Unit ,
146200 ) {
147201 val widgetType = service.widgetType
148202 _refreshStates .update { it + (widgetType to true ) }
0 commit comments