diff --git a/app/build.gradle b/app/build.gradle index deb9afdca..c3b04a84f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -104,6 +104,7 @@ dependencies { implementation "androidx.constraintlayout:constraintlayout-compose:1.0.1" implementation "com.google.dagger:dagger:2.50" + implementation 'androidx.glance:glance-appwidget:1.1.0' ksp "com.google.dagger:dagger-compiler:2.50" implementation "androidx.room:room-runtime:2.6.1" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 34fd6830c..59b5efeea 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -31,6 +31,17 @@ + + + + + + + + + QuickPairsWidgetRefreshWorker( + params = workerParameters, + context = appContext, + ) + + else -> null + } + } +} diff --git a/app/src/main/java/dev/arkbuilders/rate/di/AppComponent.kt b/app/src/main/java/dev/arkbuilders/rate/di/AppComponent.kt index 2429e6ef9..63766677c 100644 --- a/app/src/main/java/dev/arkbuilders/rate/di/AppComponent.kt +++ b/app/src/main/java/dev/arkbuilders/rate/di/AppComponent.kt @@ -5,6 +5,7 @@ import android.content.Context import dagger.BindsInstance import dagger.Component import dev.arkbuilders.rate.data.repo.PortfolioRepoImpl +import dev.arkbuilders.rate.data.repo.QuickRepoImpl import dev.arkbuilders.rate.data.repo.currency.CurrencyRepoImpl import dev.arkbuilders.rate.data.worker.AppWorkerFactory import dev.arkbuilders.rate.data.worker.CurrencyMonitorWorker @@ -14,6 +15,7 @@ import dev.arkbuilders.rate.di.module.RepoModule import dev.arkbuilders.rate.domain.repo.NetworkStatus import dev.arkbuilders.rate.domain.repo.Prefs import dev.arkbuilders.rate.domain.usecase.CalcFrequentCurrUseCase +import dev.arkbuilders.rate.domain.usecase.ConvertWithRateUseCase import dev.arkbuilders.rate.presentation.pairalert.AddPairAlertViewModelFactory import dev.arkbuilders.rate.presentation.pairalert.PairAlertViewModelFactory import dev.arkbuilders.rate.presentation.portfolio.AddAssetViewModelFactory @@ -62,6 +64,10 @@ interface AppComponent { fun assetsRepo(): PortfolioRepoImpl + fun quickRepo(): QuickRepoImpl + + fun convertUseCase(): ConvertWithRateUseCase + fun inject(currencyMonitorWorker: CurrencyMonitorWorker) fun calcFrequentCurrUseCase(): CalcFrequentCurrUseCase diff --git a/app/src/main/java/dev/arkbuilders/rate/presentation/App.kt b/app/src/main/java/dev/arkbuilders/rate/presentation/App.kt index 3685ff711..75dd1c98e 100644 --- a/app/src/main/java/dev/arkbuilders/rate/presentation/App.kt +++ b/app/src/main/java/dev/arkbuilders/rate/presentation/App.kt @@ -4,6 +4,7 @@ import android.app.Application import androidx.work.Configuration import androidx.work.Constraints import androidx.work.ExistingPeriodicWorkPolicy +import androidx.work.ListenableWorker import androidx.work.NetworkType import androidx.work.PeriodicWorkRequest import androidx.work.WorkManager @@ -11,6 +12,7 @@ import com.google.firebase.crashlytics.ktx.crashlytics import com.google.firebase.ktx.Firebase import dev.arkbuilders.rate.BuildConfig import dev.arkbuilders.rate.data.worker.CurrencyMonitorWorker +import dev.arkbuilders.rate.data.worker.QuickPairsWidgetRefreshWorker import dev.arkbuilders.rate.di.DIManager import dev.arkbuilders.rate.domain.repo.PreferenceKey import kotlinx.coroutines.CoroutineScope @@ -26,7 +28,8 @@ class App : Application(), Configuration.Provider { DIManager.init(this) initCrashlytics() - initWorker() + initWorker(CurrencyMonitorWorker::class.java, CurrencyMonitorWorker.NAME) + initWorker(QuickPairsWidgetRefreshWorker::class.java, QuickPairsWidgetRefreshWorker.NAME) } private fun initCrashlytics() = @@ -41,9 +44,11 @@ class App : Application(), Configuration.Provider { Firebase.crashlytics.setCrashlyticsCollectionEnabled(collect) } - private fun initWorker() { + private fun initWorker( + workerClass: Class, + workerName: String, + ) { val workManager = WorkManager.getInstance(this) - val constraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) @@ -51,14 +56,14 @@ class App : Application(), Configuration.Provider { val workRequest = PeriodicWorkRequest.Builder( - CurrencyMonitorWorker::class.java, + workerClass, 8L, TimeUnit.HOURS, ).setConstraints(constraints) .build() workManager.enqueueUniquePeriodicWork( - CurrencyMonitorWorker.NAME, + workerName, ExistingPeriodicWorkPolicy.KEEP, workRequest, ) diff --git a/app/src/main/java/dev/arkbuilders/rate/presentation/MainActivity.kt b/app/src/main/java/dev/arkbuilders/rate/presentation/MainActivity.kt index a350dafc3..f69afdb07 100644 --- a/app/src/main/java/dev/arkbuilders/rate/presentation/MainActivity.kt +++ b/app/src/main/java/dev/arkbuilders/rate/presentation/MainActivity.kt @@ -1,10 +1,12 @@ package dev.arkbuilders.rate.presentation +import android.content.Intent import android.graphics.Color import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.core.view.WindowCompat +import dev.arkbuilders.rate.presentation.quick.glancewidget.QuickPairsWidgetReceiver import dev.arkbuilders.rate.presentation.theme.ARKRateTheme class MainActivity : ComponentActivity() { @@ -18,4 +20,13 @@ class MainActivity : ComponentActivity() { } } } + + override fun onStop() { + sendBroadcast( + Intent(this, QuickPairsWidgetReceiver::class.java).apply { + action = QuickPairsWidgetReceiver.PINNED_PAIRS_REFRESH + }, + ) + super.onStop() + } } diff --git a/app/src/main/java/dev/arkbuilders/rate/presentation/quick/QuickScreen.kt b/app/src/main/java/dev/arkbuilders/rate/presentation/quick/QuickScreen.kt index b2a47d305..772a9c52a 100644 --- a/app/src/main/java/dev/arkbuilders/rate/presentation/quick/QuickScreen.kt +++ b/app/src/main/java/dev/arkbuilders/rate/presentation/quick/QuickScreen.kt @@ -1,11 +1,8 @@ -@file:OptIn( - ExperimentalComposeUiApi::class, - ExperimentalFoundationApi::class, -) - package dev.arkbuilders.rate.presentation.quick -import androidx.compose.foundation.ExperimentalFoundationApi +import android.app.Activity +import android.content.Context +import android.content.ContextWrapper import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable @@ -29,12 +26,12 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color @@ -61,6 +58,7 @@ import dev.arkbuilders.rate.domain.model.CurrencyName import dev.arkbuilders.rate.domain.model.PinnedQuickPair import dev.arkbuilders.rate.domain.model.QuickPair import dev.arkbuilders.rate.presentation.destinations.AddQuickScreenDestination +import dev.arkbuilders.rate.presentation.quick.glancewidget.action.AddNewPairAction.Companion.ADD_NEW_PAIR import dev.arkbuilders.rate.presentation.theme.ArkColor import dev.arkbuilders.rate.presentation.ui.AppButton import dev.arkbuilders.rate.presentation.ui.AppHorDiv16 @@ -93,7 +91,15 @@ fun QuickScreen(navigator: DestinationsNavigator) { val state by viewModel.collectAsState() val snackState = remember { SnackbarHostState() } val ctx = LocalContext.current - + LaunchedEffect(key1 = Unit) { + val activity = ctx.findActivity() + val intent = activity?.intent + val createNewPair = intent?.getStringExtra(ADD_NEW_PAIR) ?: "" + if (createNewPair.isNotEmpty()) { + navigator.navigate(AddQuickScreenDestination()) + intent?.removeExtra(ADD_NEW_PAIR) + } + } viewModel.collectSideEffect { effect -> when (effect) { is QuickScreenEffect.ShowSnackbarAdded -> @@ -585,3 +591,10 @@ private fun QuickEmpty(navigator: DestinationsNavigator) { } } } + +fun Context.findActivity(): Activity? = + when (this) { + is Activity -> this + is ContextWrapper -> baseContext.findActivity() + else -> null + } diff --git a/app/src/main/java/dev/arkbuilders/rate/presentation/quick/glancewidget/QuickPairItem.kt b/app/src/main/java/dev/arkbuilders/rate/presentation/quick/glancewidget/QuickPairItem.kt new file mode 100644 index 000000000..99f7e7d53 --- /dev/null +++ b/app/src/main/java/dev/arkbuilders/rate/presentation/quick/glancewidget/QuickPairItem.kt @@ -0,0 +1,92 @@ +package dev.arkbuilders.rate.presentation.quick.glancewidget + +import android.content.Context +import androidx.compose.runtime.Composable +import androidx.compose.ui.unit.dp +import androidx.glance.GlanceModifier +import androidx.glance.Image +import androidx.glance.ImageProvider +import androidx.glance.layout.Alignment +import androidx.glance.layout.Column +import androidx.glance.layout.Row +import androidx.glance.layout.Spacer +import androidx.glance.layout.fillMaxWidth +import androidx.glance.layout.padding +import androidx.glance.layout.size +import androidx.glance.layout.width +import androidx.glance.text.FontWeight +import androidx.glance.text.Text +import androidx.glance.text.TextStyle +import androidx.glance.unit.ColorProvider +import dev.arkbuilders.rate.data.CurrUtils +import dev.arkbuilders.rate.domain.model.PinnedQuickPair +import dev.arkbuilders.rate.presentation.theme.ArkColor +import dev.arkbuilders.rate.presentation.utils.IconUtils + +@Composable +fun QuickPairItem( + quick: PinnedQuickPair, + context: Context, +) { + Row( + modifier = GlanceModifier.padding(vertical = 2.dp), + ) { + Image( + modifier = GlanceModifier.size(24.dp), + provider = + ImageProvider( + IconUtils.iconForCurrCode( + context, + quick.pair.from, + ), + ), + contentDescription = null, + ) + Column( + modifier = GlanceModifier.padding(start = 8.dp), + verticalAlignment = Alignment.Vertical.CenterVertically, + ) { + Text( + text = "${quick.pair.from} to ${quick.pair.to.joinToString( + separator = ", ", + ) { it.code }}", + style = + TextStyle( + color = ColorProvider(ArkColor.TextPrimary), + fontWeight = FontWeight.Medium, + ), + ) + + Text( + text = "${CurrUtils.prepareToDisplay(quick.pair.amount)} ${quick.pair.from} = ", + style = + TextStyle( + color = ColorProvider(ArkColor.TextTertiary), + ), + ) + for (toAmount in quick.actualTo) { + Row(modifier = GlanceModifier.fillMaxWidth()) { + Image( + modifier = GlanceModifier.size(16.dp), + provider = + ImageProvider( + IconUtils.iconForCurrCode( + context, + toAmount.code, + ), + ), + contentDescription = null, + ) + Spacer(modifier = GlanceModifier.width(4.dp)) + Text( + text = "${CurrUtils.prepareToDisplay(toAmount.value)} ${toAmount.code}", + style = + TextStyle( + color = ColorProvider(ArkColor.TextTertiary), + ), + ) + } + } + } + } +} diff --git a/app/src/main/java/dev/arkbuilders/rate/presentation/quick/glancewidget/QuickPairsWidget.kt b/app/src/main/java/dev/arkbuilders/rate/presentation/quick/glancewidget/QuickPairsWidget.kt new file mode 100644 index 000000000..9db64cf5e --- /dev/null +++ b/app/src/main/java/dev/arkbuilders/rate/presentation/quick/glancewidget/QuickPairsWidget.kt @@ -0,0 +1,125 @@ +package dev.arkbuilders.rate.presentation.quick.glancewidget + +import android.content.Context +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.datastore.preferences.core.Preferences +import androidx.glance.GlanceId +import androidx.glance.GlanceModifier +import androidx.glance.Image +import androidx.glance.ImageProvider +import androidx.glance.action.clickable +import androidx.glance.appwidget.GlanceAppWidget +import androidx.glance.appwidget.action.actionRunCallback +import androidx.glance.appwidget.lazy.LazyColumn +import androidx.glance.appwidget.lazy.items +import androidx.glance.appwidget.provideContent +import androidx.glance.background +import androidx.glance.currentState +import androidx.glance.layout.Alignment +import androidx.glance.layout.Column +import androidx.glance.layout.Row +import androidx.glance.layout.Spacer +import androidx.glance.layout.fillMaxHeight +import androidx.glance.layout.fillMaxSize +import androidx.glance.layout.fillMaxWidth +import androidx.glance.layout.height +import androidx.glance.layout.padding +import androidx.glance.layout.size +import androidx.glance.text.FontWeight +import androidx.glance.text.Text +import androidx.glance.text.TextStyle +import androidx.glance.unit.ColorProvider +import com.google.gson.GsonBuilder +import dev.arkbuilders.rate.R +import dev.arkbuilders.rate.domain.model.PinnedQuickPair +import dev.arkbuilders.rate.presentation.quick.glancewidget.action.AddNewPairAction +import dev.arkbuilders.rate.presentation.quick.glancewidget.action.OpenAppAction +import dev.arkbuilders.rate.presentation.theme.ArkColor + +class QuickPairsWidget : GlanceAppWidget() { + override suspend fun provideGlance( + context: Context, + id: GlanceId, + ) { + provideContent { + val prefs = currentState() + val quickPairsString = prefs[QuickPairsWidgetReceiver.quickDisplayPairs] + val quickPairsList = quickPairsString?.let { parseQuickPairs(it) } + Column( + modifier = + GlanceModifier.fillMaxSize().background(Color.White) + .padding(horizontal = 12.dp), + ) { + Row( + modifier = GlanceModifier.fillMaxWidth().padding(top = 8.dp), + verticalAlignment = Alignment.Vertical.CenterVertically, + ) { + Image( + modifier = + GlanceModifier.size(24.dp).padding(4.dp) + .clickable(actionRunCallback()), + provider = + ImageProvider( + R.drawable.ic_about_logo, + ), + contentDescription = null, + ) + Text( + modifier = GlanceModifier.defaultWeight(), + text = context.getString(R.string.quick_pinned_pairs), + style = + TextStyle( + color = ColorProvider(ArkColor.TextTertiary), + fontWeight = FontWeight.Medium, + ), + ) + Image( + modifier = + GlanceModifier.size(24.dp).padding(4.dp) + .clickable(actionRunCallback()), + provider = + ImageProvider( + R.drawable.ic_add_circle, + ), + contentDescription = null, + ) + Text( + modifier = + GlanceModifier + .clickable(actionRunCallback()), + text = context.getString(R.string.quick_open_app), + style = + TextStyle( + color = ColorProvider(ArkColor.Primary), + fontWeight = FontWeight.Medium, + ), + ) + } + LazyColumn(modifier = GlanceModifier.fillMaxHeight()) { + quickPairsList?.let { pairs -> + items(pairs) { quick -> + Column { + QuickPairItem( + quick = quick, + context = context, + ) + Spacer( + modifier = + GlanceModifier.fillMaxWidth().height(1.dp) + .background(Color.Gray.copy(alpha = 0.2f)) + .padding(vertical = 2.dp), + ) + } + } + } + } + } + } + } + + private fun parseQuickPairs(quickPairsString: String): List { + val gson = GsonBuilder().create() + return gson.fromJson(quickPairsString, Array::class.java).toList() + } +} diff --git a/app/src/main/java/dev/arkbuilders/rate/presentation/quick/glancewidget/QuickPairsWidgetReceiver.kt b/app/src/main/java/dev/arkbuilders/rate/presentation/quick/glancewidget/QuickPairsWidgetReceiver.kt new file mode 100644 index 000000000..20dd7c402 --- /dev/null +++ b/app/src/main/java/dev/arkbuilders/rate/presentation/quick/glancewidget/QuickPairsWidgetReceiver.kt @@ -0,0 +1,122 @@ +package dev.arkbuilders.rate.presentation.quick.glancewidget + +import android.appwidget.AppWidgetManager +import android.content.Context +import android.content.Intent +import androidx.datastore.preferences.core.stringPreferencesKey +import androidx.glance.appwidget.GlanceAppWidget +import androidx.glance.appwidget.GlanceAppWidgetManager +import androidx.glance.appwidget.GlanceAppWidgetReceiver +import androidx.glance.appwidget.state.updateAppWidgetState +import androidx.glance.state.PreferencesGlanceStateDefinition +import com.google.gson.GsonBuilder +import dev.arkbuilders.rate.di.DIManager +import dev.arkbuilders.rate.domain.model.PinnedQuickPair +import dev.arkbuilders.rate.domain.model.QuickPair +import dev.arkbuilders.rate.domain.repo.QuickRepo +import dev.arkbuilders.rate.domain.usecase.ConvertWithRateUseCase +import dev.arkbuilders.rate.presentation.MainActivity +import dev.arkbuilders.rate.presentation.quick.QuickScreenPage +import dev.arkbuilders.rate.presentation.quick.glancewidget.action.AddNewPairAction +import dev.arkbuilders.rate.presentation.quick.glancewidget.action.OpenAppAction +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import timber.log.Timber +import java.time.OffsetDateTime + +class QuickPairsWidgetReceiver( + private val quickRepo: QuickRepo = DIManager.component.quickRepo(), + private val convertUseCase: ConvertWithRateUseCase = DIManager.component.convertUseCase(), +) : GlanceAppWidgetReceiver() { + private val coroutineScope = MainScope() + + override val glanceAppWidget: GlanceAppWidget = QuickPairsWidget() + + override fun onReceive( + context: Context, + intent: Intent, + ) { + super.onReceive(context, intent) + val action = intent.action + Timber.d(action) + when (action) { + AppWidgetManager.ACTION_APPWIDGET_ENABLED, PINNED_PAIRS_REFRESH -> + getQuickPairs(context) + OpenAppAction.OPEN_APP -> { + context.startActivity( + Intent(context, MainActivity::class.java).apply { + setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + }, + ) + } + AddNewPairAction.ADD_NEW_PAIR -> { + context.startActivity( + Intent(context, MainActivity::class.java).apply { + putExtra(AddNewPairAction.ADD_NEW_PAIR, "ADD_NEW_PAIR") + setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + }, + ) + } + } + } + + override fun onUpdate( + context: Context, + appWidgetManager: AppWidgetManager, + appWidgetIds: IntArray, + ) { + super.onUpdate(context, appWidgetManager, appWidgetIds) + getQuickPairs(context) + } + + private fun getQuickPairs(context: Context) { + Timber.d("Get quick pairs for widget") + quickRepo.allFlow().onEach { quick -> + val pages = mapPairsToPages(quick) + val quickDisplayPair = pages.first().pinned + val quickPairs = GsonBuilder().create().toJson(quickDisplayPair) + val glanceIds = + GlanceAppWidgetManager(context).getGlanceIds(QuickPairsWidget::class.java) + for (glanceId in glanceIds) { + glanceId.let { id -> + updateAppWidgetState(context, PreferencesGlanceStateDefinition, id) { pref -> + pref.toMutablePreferences().apply { + this[quickDisplayPairs] = quickPairs + } + } + glanceAppWidget.update(context, id) + } + } + }.launchIn(coroutineScope) + } + + private suspend fun mapPairsToPages(pairs: List): List { + val pages = + pairs + .reversed() + .groupBy { it.group } + .map { (group, pairs) -> + val pinnedQuickPairs = pairs.filter { it.isPinned() } + val pinnedMappedQuickPairs = pinnedQuickPairs.map { mapPairToPinned(it) } + val sortedPinned = + pinnedMappedQuickPairs.sortedByDescending { it.pair.pinnedDate } + QuickScreenPage(group, sortedPinned, listOf()) + } + return pages + } + + private suspend fun mapPairToPinned(pair: QuickPair): PinnedQuickPair { + val actualTo = + pair.to.map { to -> + val (amount, _) = convertUseCase.invoke(pair.from, pair.amount, to.code) + amount + } + return PinnedQuickPair(pair, actualTo, OffsetDateTime.now()) + } + + companion object { + val quickDisplayPairs = stringPreferencesKey("quick_pair_display") + const val PINNED_PAIRS_REFRESH = "PINNED_PAIRS_REFRESH" + } +} diff --git a/app/src/main/java/dev/arkbuilders/rate/presentation/quick/glancewidget/action/AddNewPairAction.kt b/app/src/main/java/dev/arkbuilders/rate/presentation/quick/glancewidget/action/AddNewPairAction.kt new file mode 100644 index 000000000..9cf90980b --- /dev/null +++ b/app/src/main/java/dev/arkbuilders/rate/presentation/quick/glancewidget/action/AddNewPairAction.kt @@ -0,0 +1,26 @@ +package dev.arkbuilders.rate.presentation.quick.glancewidget.action + +import android.content.Context +import android.content.Intent +import androidx.glance.GlanceId +import androidx.glance.action.ActionParameters +import androidx.glance.appwidget.action.ActionCallback +import dev.arkbuilders.rate.presentation.quick.glancewidget.QuickPairsWidgetReceiver + +class AddNewPairAction : ActionCallback { + override suspend fun onAction( + context: Context, + glanceId: GlanceId, + parameters: ActionParameters, + ) { + val intent = + Intent(context, QuickPairsWidgetReceiver::class.java).apply { + action = ADD_NEW_PAIR + } + context.sendBroadcast(intent) + } + + companion object { + const val ADD_NEW_PAIR = "QUICK_PAIRS_WIDGET_NEW_PAIR" + } +} diff --git a/app/src/main/java/dev/arkbuilders/rate/presentation/quick/glancewidget/action/OpenAppAction.kt b/app/src/main/java/dev/arkbuilders/rate/presentation/quick/glancewidget/action/OpenAppAction.kt new file mode 100644 index 000000000..02c1e4958 --- /dev/null +++ b/app/src/main/java/dev/arkbuilders/rate/presentation/quick/glancewidget/action/OpenAppAction.kt @@ -0,0 +1,26 @@ +package dev.arkbuilders.rate.presentation.quick.glancewidget.action + +import android.content.Context +import android.content.Intent +import androidx.glance.GlanceId +import androidx.glance.action.ActionParameters +import androidx.glance.appwidget.action.ActionCallback +import dev.arkbuilders.rate.presentation.quick.glancewidget.QuickPairsWidgetReceiver + +class OpenAppAction : ActionCallback { + override suspend fun onAction( + context: Context, + glanceId: GlanceId, + parameters: ActionParameters, + ) { + val intent = + Intent(context, QuickPairsWidgetReceiver::class.java).apply { + action = OPEN_APP + } + context.sendBroadcast(intent) + } + + companion object { + const val OPEN_APP = "QUICK_PAIRS_WIDGET_OPEN_APP" + } +} diff --git a/app/src/main/res/drawable/ic_add_circle.xml b/app/src/main/res/drawable/ic_add_circle.xml new file mode 100644 index 000000000..eed3306a0 --- /dev/null +++ b/app/src/main/res/drawable/ic_add_circle.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1a36d6cc1..7492cde00 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -22,6 +22,7 @@ The calculation has been deleted You deleted %1$s calculation %1$s to %2$s + Open app Asset detail diff --git a/app/src/main/res/xml/glance_widget_provider.xml b/app/src/main/res/xml/glance_widget_provider.xml new file mode 100644 index 000000000..b02129196 --- /dev/null +++ b/app/src/main/res/xml/glance_widget_provider.xml @@ -0,0 +1,13 @@ + + + \ No newline at end of file