/* * Copyright (C) 2022 E FOUNDATION * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package foundation.e.advancedprivacy import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetProvider import android.content.Context import android.os.Bundle import foundation.e.advancedprivacy.domain.usecases.GetQuickPrivacyStateUseCase import foundation.e.advancedprivacy.domain.usecases.TrackersStatisticsUseCase import foundation.e.advancedprivacy.widget.State import foundation.e.advancedprivacy.widget.render import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.sample import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import java.time.temporal.ChronoUnit /** * Implementation of App Widget functionality. */ class Widget : AppWidgetProvider() { override fun onUpdate( context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray ) { render(context, state.value, appWidgetManager) } override fun onEnabled(context: Context) { // Enter relevant functionality for when the first widget is created } override fun onDisabled(context: Context) { // Enter relevant functionality for when the last widget is disabled } companion object { private var updateWidgetJob: Job? = null private var state: StateFlow = MutableStateFlow(State()) private const val DARK_TEXT_KEY = "foundation.e.blisslauncher.WIDGET_OPTION_DARK_TEXT" var isDarkText = false @OptIn(FlowPreview::class) private fun initState( getPrivacyStateUseCase: GetQuickPrivacyStateUseCase, trackersStatisticsUseCase: TrackersStatisticsUseCase, coroutineScope: CoroutineScope ): StateFlow { return combine( getPrivacyStateUseCase.quickPrivacyState, getPrivacyStateUseCase.trackerMode, getPrivacyStateUseCase.isLocationHidden, getPrivacyStateUseCase.ipScramblingMode, ) { quickPrivacyState, trackerMode, isLocationHidden, ipScramblingMode -> State( quickPrivacyState = quickPrivacyState, trackerMode = trackerMode, isLocationHidden = isLocationHidden, ipScramblingMode = ipScramblingMode ) }.sample(50) .combine( merge( trackersStatisticsUseCase.listenUpdates() .onStart { emit(Unit) } .debounce(5000), flow { while (true) { emit(Unit) delay(ChronoUnit.HOURS.duration.toMillis()) } } ) ) { state, _ -> state.copy( dayStatistics = trackersStatisticsUseCase.getDayTrackersCalls(), activeTrackersCount = trackersStatisticsUseCase.getDayTrackersCount() ) }.stateIn( scope = coroutineScope, started = SharingStarted.Eagerly, initialValue = State() ) } @OptIn(DelicateCoroutinesApi::class) fun startListening( appContext: Context, getPrivacyStateUseCase: GetQuickPrivacyStateUseCase, trackersStatisticsUseCase: TrackersStatisticsUseCase, ) { state = initState( getPrivacyStateUseCase, trackersStatisticsUseCase, GlobalScope ) updateWidgetJob?.cancel() updateWidgetJob = GlobalScope.launch(Dispatchers.Main) { state.collect { render(appContext, it, AppWidgetManager.getInstance(appContext)) } } } } override fun onAppWidgetOptionsChanged( context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int, newOptions: Bundle? ) { super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions) if (newOptions != null) { isDarkText = newOptions.getBoolean(DARK_TEXT_KEY) } render(context, state.value, appWidgetManager) } }