diff --git a/core/ui/src/main/java/com/sadellie/unitto/core/ui/Shortcuts.kt b/core/ui/src/main/java/com/sadellie/unitto/core/ui/Shortcuts.kt index 63b3ce9f..07cb0ed9 100644 --- a/core/ui/src/main/java/com/sadellie/unitto/core/ui/Shortcuts.kt +++ b/core/ui/src/main/java/com/sadellie/unitto/core/ui/Shortcuts.kt @@ -23,7 +23,6 @@ import android.app.PendingIntent.FLAG_IMMUTABLE import android.content.Context import android.content.Intent import android.net.Uri -import android.widget.Toast import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.core.content.pm.ShortcutInfoCompat @@ -82,7 +81,7 @@ fun Context.addShortcut( PendingIntent.getBroadcast(context, 0, shortCutIntent, FLAG_IMMUTABLE).intentSender ) } catch (e: Exception) { - Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show() + showToast(context, e.message ?: "Error") } } diff --git a/core/ui/src/main/java/com/sadellie/unitto/core/ui/UIUtils.kt b/core/ui/src/main/java/com/sadellie/unitto/core/ui/UIUtils.kt index 68b6f554..42ddf9af 100644 --- a/core/ui/src/main/java/com/sadellie/unitto/core/ui/UIUtils.kt +++ b/core/ui/src/main/java/com/sadellie/unitto/core/ui/UIUtils.kt @@ -35,9 +35,17 @@ fun openLink(mContext: Context, url: String) { try { mContext.startActivity(Intent(Intent.ACTION_VIEW).setData(Uri.parse(url))) } catch (e: ActivityNotFoundException) { - Toast.makeText(mContext, R.string.error_label, Toast.LENGTH_SHORT).show() + showToast(mContext, mContext.getString(R.string.error_label)) } } +fun showToast( + mContext: Context, + text: String, + duration: Int = Toast.LENGTH_SHORT +) { + Toast.makeText(mContext, text, duration).show() +} + @Composable fun isPortrait() = LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT diff --git a/data/database/src/main/java/com/sadellie/unitto/data/database/CurrencyRatesDao.kt b/data/database/src/main/java/com/sadellie/unitto/data/database/CurrencyRatesDao.kt index 9f8838f4..991aefa2 100644 --- a/data/database/src/main/java/com/sadellie/unitto/data/database/CurrencyRatesDao.kt +++ b/data/database/src/main/java/com/sadellie/unitto/data/database/CurrencyRatesDao.kt @@ -22,6 +22,7 @@ import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query +import kotlinx.coroutines.flow.Flow @Dao interface CurrencyRatesDao { @@ -32,6 +33,9 @@ interface CurrencyRatesDao { @Query("SELECT DISTINCT * FROM currency_rates WHERE timestamp = (SELECT MAX(timestamp) FROM currency_rates) AND base_unit_id = :baseId") suspend fun getLatestRates(baseId: String): List + @Query("SELECT COUNT(*) from currency_rates") + fun size(): Flow + @Query("DELETE FROM currency_rates") suspend fun clear() } diff --git a/feature/datecalculator/src/main/java/com/sadellie/unitto/feature/datecalculator/addsubtract/AddSubtractPage.kt b/feature/datecalculator/src/main/java/com/sadellie/unitto/feature/datecalculator/addsubtract/AddSubtractPage.kt index ea380c43..0c59ea84 100644 --- a/feature/datecalculator/src/main/java/com/sadellie/unitto/feature/datecalculator/addsubtract/AddSubtractPage.kt +++ b/feature/datecalculator/src/main/java/com/sadellie/unitto/feature/datecalculator/addsubtract/AddSubtractPage.kt @@ -23,7 +23,6 @@ import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent import android.provider.CalendarContract -import android.widget.Toast import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn @@ -73,6 +72,7 @@ import com.sadellie.unitto.core.base.R import com.sadellie.unitto.core.ui.common.textfield.addTokens import com.sadellie.unitto.core.ui.common.textfield.deleteTokens import com.sadellie.unitto.core.ui.isPortrait +import com.sadellie.unitto.core.ui.showToast import com.sadellie.unitto.feature.datecalculator.components.AddSubtractKeyboard import com.sadellie.unitto.feature.datecalculator.components.DateTimeDialogs import com.sadellie.unitto.feature.datecalculator.components.DateTimeSelectorBlock @@ -340,7 +340,7 @@ private fun Context.addEvent(start: ZonedDateTime, end: ZonedDateTime) { try { startActivity(intent) } catch (e: ActivityNotFoundException) { - Toast.makeText(this, R.string.error_label, Toast.LENGTH_SHORT).show() + showToast(this, this.getString(R.string.error_label)) } } diff --git a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/SettingsScreen.kt b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/SettingsScreen.kt index ccd8e5bd..9b12f1f4 100644 --- a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/SettingsScreen.kt +++ b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/SettingsScreen.kt @@ -18,8 +18,23 @@ package com.sadellie.unitto.feature.settings +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.expandVertically +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkVertically +import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Cached import androidx.compose.material.icons.filled.Calculate @@ -29,12 +44,24 @@ import androidx.compose.material.icons.filled.Palette import androidx.compose.material.icons.filled.RateReview import androidx.compose.material.icons.filled.SwapHoriz import androidx.compose.material.icons.filled.Vibration +import androidx.compose.material.icons.filled.Warning import androidx.compose.material.icons.filled._123 +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.sadellie.unitto.core.base.BuildConfig @@ -44,6 +71,7 @@ import com.sadellie.unitto.core.ui.common.NavigateUpButton import com.sadellie.unitto.core.ui.common.UnittoListItem import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar import com.sadellie.unitto.core.ui.openLink +import com.sadellie.unitto.core.ui.showToast import com.sadellie.unitto.data.userprefs.GeneralPreferences import com.sadellie.unitto.feature.settings.navigation.aboutRoute import com.sadellie.unitto.feature.settings.navigation.calculatorSettingsRoute @@ -59,13 +87,15 @@ internal fun SettingsRoute( navControllerAction: (String) -> Unit, ) { val userPrefs = viewModel.userPrefs.collectAsStateWithLifecycle() + val cachePercentage = viewModel.cachePercentage.collectAsStateWithLifecycle() SettingsScreen( userPrefs = userPrefs.value, navigateUp = navigateUp, navControllerAction = navControllerAction, updateVibrations = viewModel::updateVibrations, - clearCache = viewModel::clearCache + cachePercentage = cachePercentage.value, + clearCache = viewModel::clearCache, ) } @@ -75,6 +105,7 @@ private fun SettingsScreen( navigateUp: () -> Unit, navControllerAction: (String) -> Unit, updateVibrations: (Boolean) -> Unit, + cachePercentage: Float, clearCache: () -> Unit, ) { val mContext = LocalContext.current @@ -83,101 +114,122 @@ private fun SettingsScreen( title = stringResource(R.string.settings_screen), navigationIcon = { NavigateUpButton(navigateUp) } ) { padding -> - LazyColumn(contentPadding = padding) { + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .padding(padding) + ) { + UnittoListItem( + icon = Icons.Default.Palette, + iconDescription = stringResource(R.string.display_settings), + headlineText = stringResource(R.string.display_settings), + supportingText = stringResource(R.string.theme_setting_support), + modifier = Modifier.clickable { navControllerAction(displayRoute) } + ) - item("theme") { - UnittoListItem( - icon = Icons.Default.Palette, - iconDescription = stringResource(R.string.display_settings), - headlineText = stringResource(R.string.display_settings), - supportingText = stringResource(R.string.theme_setting_support), - modifier = Modifier.clickable { navControllerAction(displayRoute) } - ) - } + UnittoListItem( + icon = Icons.Default.Home, + iconDescription = stringResource(R.string.starting_screen_setting), + headlineText = stringResource(R.string.starting_screen_setting), + supportingText = stringResource(R.string.starting_screen_setting_support), + modifier = Modifier.clickable { navControllerAction(startingScreenRoute) } + ) - item("starting screen") { - UnittoListItem( - icon = Icons.Default.Home, - iconDescription = stringResource(R.string.starting_screen_setting), - headlineText = stringResource(R.string.starting_screen_setting), - supportingText = stringResource(R.string.starting_screen_setting_support), - modifier = Modifier.clickable { navControllerAction(startingScreenRoute) } - ) - } + UnittoListItem( + icon = Icons.Default._123, + iconDescription = stringResource(R.string.formatting_setting), + headlineText = stringResource(R.string.formatting_setting), + supportingText = stringResource(R.string.formatting_setting_support), + modifier = Modifier.clickable { navControllerAction(formattingRoute) } + ) - item("formatting") { - UnittoListItem( - icon = Icons.Default._123, - iconDescription = stringResource(R.string.formatting_setting), - headlineText = stringResource(R.string.formatting_setting), - supportingText = stringResource(R.string.formatting_setting_support), - modifier = Modifier.clickable { navControllerAction(formattingRoute) } - ) - } + UnittoListItem( + icon = Icons.Default.Calculate, + iconDescription = stringResource(R.string.calculator), + headlineText = stringResource(R.string.calculator), + supportingText = stringResource(R.string.calculator_settings_support), + modifier = Modifier.clickable { navControllerAction(calculatorSettingsRoute) } + ) - item("calculator") { - UnittoListItem( - icon = Icons.Default.Calculate, - iconDescription = stringResource(R.string.calculator), - headlineText = stringResource(R.string.calculator), - supportingText = stringResource(R.string.calculator_settings_support), - modifier = Modifier.clickable { navControllerAction(calculatorSettingsRoute) } - ) - } + UnittoListItem( + icon = Icons.Default.SwapHoriz, + iconDescription = stringResource(R.string.unit_converter), + headlineText = stringResource(R.string.unit_converter), + supportingText = stringResource(R.string.converter_settings_support), + modifier = Modifier.clickable { navControllerAction(converterSettingsRoute) } + ) - item("unit converter") { - UnittoListItem( - icon = Icons.Default.SwapHoriz, - iconDescription = stringResource(R.string.unit_converter), - headlineText = stringResource(R.string.unit_converter), - supportingText = stringResource(R.string.converter_settings_support), - modifier = Modifier.clickable { navControllerAction(converterSettingsRoute) } - ) - } + Header(stringResource(R.string.additional_settings_group)) - item("additional") { Header(stringResource(R.string.additional_settings_group)) } + UnittoListItem( + icon = Icons.Default.Vibration, + iconDescription = stringResource(R.string.enable_vibrations), + headlineText = stringResource(R.string.enable_vibrations), + supportingText = stringResource(R.string.enable_vibrations_support), + modifier = Modifier.clickable { navControllerAction(converterSettingsRoute) }, + switchState = userPrefs.enableVibrations, + onSwitchChange = updateVibrations + ) - item("vibrations") { - UnittoListItem( - icon = Icons.Default.Vibration, - iconDescription = stringResource(R.string.enable_vibrations), - headlineText = stringResource(R.string.enable_vibrations), - supportingText = stringResource(R.string.enable_vibrations_support), - modifier = Modifier.clickable { navControllerAction(converterSettingsRoute) }, - switchState = userPrefs.enableVibrations, - onSwitchChange = updateVibrations - ) - } - - item("clear cache") { + AnimatedVisibility( + visible = cachePercentage > 0, + enter = expandVertically() + fadeIn(), + exit = shrinkVertically() + fadeOut(), + ) { UnittoListItem( headlineText = stringResource(R.string.clear_cache), icon = Icons.Default.Cached, iconDescription = stringResource(R.string.clear_cache), - modifier = Modifier.clickable { clearCache() }, + modifier = Modifier.clickable { clearCache(); showToast(mContext, "👌") }, + trailing = { + Box( + Modifier + .clip(CircleShape) + .background(MaterialTheme.colorScheme.errorContainer) + .size(52.dp, 24.dp), + contentAlignment = Alignment.CenterStart + ) { + Box( + Modifier + .background( + MaterialTheme.colorScheme.error.copy(alpha = cachePercentage) + .compositeOver(Color.Green) + ) + .height(24.dp) + .fillMaxWidth(cachePercentage), + contentAlignment = Alignment.Center + ) { + if (cachePercentage == 1f) { + Icon( + imageVector = Icons.Default.Warning, + contentDescription = null, + tint = MaterialTheme.colorScheme.onError, + modifier = Modifier.size(18.dp) + ) + } + } + } + } ) } if (BuildConfig.STORE_LINK.isNotEmpty()) { - item("rate this app") { - UnittoListItem( - icon = Icons.Default.RateReview, - iconDescription = stringResource(R.string.rate_this_app), - headlineText = stringResource(R.string.rate_this_app), - modifier = Modifier.clickable { openLink(mContext, BuildConfig.STORE_LINK) } - ) - } - } - - item("about") { UnittoListItem( - icon = Icons.Default.Info, - iconDescription = stringResource(R.string.about_unitto), - headlineText = stringResource(R.string.about_unitto), - supportingText = stringResource(R.string.about_unitto_support), - modifier = Modifier.clickable { navControllerAction(aboutRoute) } + icon = Icons.Default.RateReview, + iconDescription = stringResource(R.string.rate_this_app), + headlineText = stringResource(R.string.rate_this_app), + modifier = Modifier.clickable { openLink(mContext, BuildConfig.STORE_LINK) } ) } + + UnittoListItem( + icon = Icons.Default.Info, + iconDescription = stringResource(R.string.about_unitto), + headlineText = stringResource(R.string.about_unitto), + supportingText = stringResource(R.string.about_unitto_support), + modifier = Modifier.clickable { navControllerAction(aboutRoute) } + ) } } } @@ -185,11 +237,14 @@ private fun SettingsScreen( @Preview @Composable private fun PreviewSettingsScreen() { + var cacheSize by remember { mutableFloatStateOf(0.9f) } + SettingsScreen( userPrefs = GeneralPreferences(), navigateUp = {}, navControllerAction = {}, updateVibrations = {}, - clearCache = {} + cachePercentage = cacheSize, + clearCache = { cacheSize = 0f } ) } diff --git a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/SettingsViewModel.kt b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/SettingsViewModel.kt index c0fe81a8..a8f7c389 100644 --- a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/SettingsViewModel.kt +++ b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/SettingsViewModel.kt @@ -25,6 +25,8 @@ import com.sadellie.unitto.data.database.CurrencyRatesDao import com.sadellie.unitto.data.userprefs.GeneralPreferences import com.sadellie.unitto.data.userprefs.UserPreferencesRepository import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import javax.inject.Inject @@ -36,6 +38,12 @@ internal class SettingsViewModel @Inject constructor( val userPrefs = userPrefsRepository.generalPrefs .stateIn(viewModelScope, GeneralPreferences()) + val cachePercentage = currencyRatesDao.size() + .map { + (it / 100_000f).coerceIn(0f, 1f) + } + .stateIn(viewModelScope, 0f) + /** * @see UserPreferencesRepository.updateVibrations */ @@ -43,7 +51,7 @@ internal class SettingsViewModel @Inject constructor( userPrefsRepository.updateVibrations(enabled) } - fun clearCache() = viewModelScope.launch { + fun clearCache() = viewModelScope.launch(Dispatchers.IO) { currencyRatesDao.clear() } } diff --git a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/about/AboutScreen.kt b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/about/AboutScreen.kt index 335d1d11..5851beb0 100644 --- a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/about/AboutScreen.kt +++ b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/about/AboutScreen.kt @@ -50,6 +50,7 @@ import com.sadellie.unitto.core.ui.common.NavigateUpButton import com.sadellie.unitto.core.ui.common.UnittoListItem import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar import com.sadellie.unitto.core.ui.openLink +import com.sadellie.unitto.core.ui.showToast import com.sadellie.unitto.data.userprefs.AboutPreferences @Composable @@ -158,7 +159,7 @@ private fun AboutScreen( supportingText = "${BuildConfig.APP_NAME} (${BuildConfig.APP_CODE})", modifier = Modifier.combinedClickable { if (prefs.enableToolsExperiment) { - Toast.makeText(mContext, "Experiments features are already enabled!", Toast.LENGTH_LONG).show() + showToast(mContext, "Experiments features are already enabled!", Toast.LENGTH_LONG) return@combinedClickable } @@ -166,7 +167,7 @@ private fun AboutScreen( if (aboutItemClick < 7) return@combinedClickable enableToolsExperiment() - Toast.makeText(mContext, "Experimental features enabled!", Toast.LENGTH_LONG).show() + showToast(mContext, "Experimental features enabled!", Toast.LENGTH_LONG) } ) }