diff --git a/app/src/main/java/com/sadellie/unitto/UnittoApp.kt b/app/src/main/java/com/sadellie/unitto/UnittoApp.kt index 1e0556a6..af8957c6 100644 --- a/app/src/main/java/com/sadellie/unitto/UnittoApp.kt +++ b/app/src/main/java/com/sadellie/unitto/UnittoApp.kt @@ -46,6 +46,7 @@ import com.sadellie.unitto.core.ui.common.isOpen import com.sadellie.unitto.core.ui.common.open import com.sadellie.unitto.core.ui.common.rememberUnittoDrawerState import com.sadellie.unitto.core.ui.model.DrawerItems +import com.sadellie.unitto.core.ui.pushDynamicShortcut import com.sadellie.unitto.core.ui.theme.AppTypographySystem import com.sadellie.unitto.core.ui.theme.AppTypographyUnitto import com.sadellie.unitto.core.ui.theme.DarkThemeColors diff --git a/core/base/src/main/java/com/sadellie/unitto/core/base/TopLevelDestinations.kt b/core/base/src/main/java/com/sadellie/unitto/core/base/TopLevelDestinations.kt index 30c802c9..36fea191 100644 --- a/core/base/src/main/java/com/sadellie/unitto/core/base/TopLevelDestinations.kt +++ b/core/base/src/main/java/com/sadellie/unitto/core/base/TopLevelDestinations.kt @@ -101,11 +101,12 @@ sealed class TopLevelDestinations( } // Shown in settings -val TOP_LEVEL_GRAPH_ROUTES: Map by lazy { - mapOf( - TopLevelDestinations.Calculator.graph to R.string.calculator, - TopLevelDestinations.Converter.graph to R.string.unit_converter, - TopLevelDestinations.DateCalculator.graph to R.string.date_calculator, +val TOP_LEVEL_DESTINATIONS by lazy { + listOf( + TopLevelDestinations.Calculator, + TopLevelDestinations.Converter, + TopLevelDestinations.DateCalculator, + // TopLevelDestinations.TimeZone, ) } diff --git a/app/src/main/java/com/sadellie/unitto/PushDynamicShortcut.kt b/core/ui/src/main/java/com/sadellie/unitto/core/ui/Shortcuts.kt similarity index 57% rename from app/src/main/java/com/sadellie/unitto/PushDynamicShortcut.kt rename to core/ui/src/main/java/com/sadellie/unitto/core/ui/Shortcuts.kt index 6a6a7bf5..63b3ce9f 100644 --- a/app/src/main/java/com/sadellie/unitto/PushDynamicShortcut.kt +++ b/core/ui/src/main/java/com/sadellie/unitto/core/ui/Shortcuts.kt @@ -16,11 +16,14 @@ * along with this program. If not, see . */ -package com.sadellie.unitto +package com.sadellie.unitto.core.ui +import android.app.PendingIntent +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 @@ -41,7 +44,56 @@ suspend fun Context.pushDynamicShortcut( val context = this@pushDynamicShortcut - val shortcut = ShortcutInfoCompat.Builder(context, route) + val shortcut = shortcutInfoCompat( + context = context, + route = route, + shortLabel = shortLabel, + longLabel = longLabel, + drawable = drawable + ) + + kotlin.runCatching { + ShortcutManagerCompat.pushDynamicShortcut(context, shortcut) + } +} + +fun Context.addShortcut( + route: String, + @StringRes shortLabel: Int, + @StringRes longLabel: Int, + @DrawableRes drawable: Int, +) { + val context = this@addShortcut + + val shortcut = shortcutInfoCompat( + context = context, + route = route, + shortLabel = shortLabel, + longLabel = longLabel, + drawable = drawable + ) + + val shortCutIntent = ShortcutManagerCompat.createShortcutResultIntent(context, shortcut) + + try { + ShortcutManagerCompat.requestPinShortcut( + context, + shortcut, + PendingIntent.getBroadcast(context, 0, shortCutIntent, FLAG_IMMUTABLE).intentSender + ) + } catch (e: Exception) { + Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show() + } +} + +private fun Context.shortcutInfoCompat( + context: Context, + route: String, + shortLabel: Int, + longLabel: Int, + drawable: Int, +): ShortcutInfoCompat { + return ShortcutInfoCompat.Builder(context, route) .setShortLabel(getString(shortLabel)) .setLongLabel(getString(longLabel)) .setIcon(IconCompat.createWithResource(context, drawable)) @@ -54,6 +106,4 @@ suspend fun Context.pushDynamicShortcut( ) ) .build() - - ShortcutManagerCompat.pushDynamicShortcut(context, shortcut) } diff --git a/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/UnittoListItem.kt b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/UnittoListItem.kt index 1e23c975..7f477bf3 100644 --- a/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/UnittoListItem.kt +++ b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/UnittoListItem.kt @@ -85,7 +85,12 @@ fun UnittoListItem( ) } } - trailingContent?.invoke() + trailingContent?.let { + ProvideColor( + color = MaterialTheme.colorScheme.onSurfaceVariant, + content = it + ) + } } } diff --git a/data/userprefs/src/main/java/com/sadellie/unitto/data/userprefs/UserPreferences.kt b/data/userprefs/src/main/java/com/sadellie/unitto/data/userprefs/UserPreferences.kt index c9439e0c..7a363374 100644 --- a/data/userprefs/src/main/java/com/sadellie/unitto/data/userprefs/UserPreferences.kt +++ b/data/userprefs/src/main/java/com/sadellie/unitto/data/userprefs/UserPreferences.kt @@ -80,7 +80,6 @@ data class AppPreferences( ) data class GeneralPreferences( - val startingScreen: String = TopLevelDestinations.Calculator.graph, val enableVibrations: Boolean = true, val middleZero: Boolean = false, ) @@ -133,6 +132,10 @@ data class AboutPreferences( val enableToolsExperiment: Boolean = false, ) +data class StartingScreenPreferences( + val startingScreen: String = TopLevelDestinations.Calculator.graph, +) + class UserPreferencesRepository @Inject constructor(private val dataStore: DataStore) { private val data = dataStore.data .catch { if (it is IOException) emit(emptyPreferences()) else throw it } @@ -160,8 +163,6 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS val generalPrefs: Flow = data .map { preferences -> GeneralPreferences( - startingScreen = preferences[PrefsKeys.STARTING_SCREEN] - ?: TopLevelDestinations.Calculator.graph, enableVibrations = preferences[PrefsKeys.ENABLE_VIBRATIONS] ?: true, middleZero = preferences[PrefsKeys.MIDDLE_ZERO] ?: false, ) @@ -245,6 +246,14 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS ) } + val startingScreenPrefs: Flow = data + .map { preferences -> + StartingScreenPreferences( + startingScreen = preferences[PrefsKeys.STARTING_SCREEN] + ?: TopLevelDestinations.Calculator.graph, + ) + } + suspend fun updateDigitsPrecision(precision: Int) { dataStore.edit { preferences -> preferences[PrefsKeys.DIGITS_PRECISION] = precision 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 980ad922..608a8b30 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 @@ -46,7 +46,6 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.sadellie.unitto.core.base.BuildConfig import com.sadellie.unitto.core.base.R -import com.sadellie.unitto.core.base.TOP_LEVEL_GRAPH_ROUTES import com.sadellie.unitto.core.ui.common.Header import com.sadellie.unitto.core.ui.common.NavigateUpButton import com.sadellie.unitto.core.ui.common.UnittoListItem @@ -58,43 +57,40 @@ import com.sadellie.unitto.feature.settings.navigation.aboutRoute import com.sadellie.unitto.feature.settings.navigation.calculatorSettingsRoute import com.sadellie.unitto.feature.settings.navigation.converterSettingsRoute import com.sadellie.unitto.feature.settings.navigation.formattingRoute +import com.sadellie.unitto.feature.settings.navigation.startingScreenRoute import com.sadellie.unitto.feature.settings.navigation.themesRoute @Composable internal fun SettingsRoute( viewModel: SettingsViewModel = hiltViewModel(), - menuButtonClick: () -> Unit, + navigateUp: () -> Unit, navControllerAction: (String) -> Unit, ) { val userPrefs = viewModel.userPrefs.collectAsStateWithLifecycle() SettingsScreen( userPrefs = userPrefs.value, - menuButtonClick = menuButtonClick, + navigateUp = navigateUp, navControllerAction = navControllerAction, updateMiddleZero = viewModel::updateMiddleZero, updateVibrations = viewModel::updateVibrations, - updateStartingScreen = viewModel::updateStartingScreen ) } @Composable private fun SettingsScreen( userPrefs: GeneralPreferences, - menuButtonClick: () -> Unit, + navigateUp: () -> Unit, navControllerAction: (String) -> Unit, updateMiddleZero: (Boolean) -> Unit, updateVibrations: (Boolean) -> Unit, - updateStartingScreen: (String) -> Unit ) { val mContext = LocalContext.current - var dialogState: DialogState by rememberSaveable { - mutableStateOf(DialogState.NONE) - } + var dialogState: DialogState by rememberSaveable { mutableStateOf(DialogState.NONE) } UnittoScreenWithLargeTopBar( title = stringResource(R.string.settings_screen), - navigationIcon = { NavigateUpButton(menuButtonClick) } + navigationIcon = { NavigateUpButton(navigateUp) } ) { padding -> LazyColumn(contentPadding = padding) { @@ -116,7 +112,7 @@ private fun SettingsScreen( 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 { dialogState = DialogState.START_SCREEN } + modifier = Modifier.clickable { navControllerAction(startingScreenRoute) } ) } @@ -225,16 +221,6 @@ private fun SettingsScreen( // Showing dialog when (dialogState) { - DialogState.START_SCREEN -> { - AlertDialogWithList( - title = stringResource(R.string.starting_screen_setting), - selectedItemIndex = userPrefs.startingScreen, - listItems = TOP_LEVEL_GRAPH_ROUTES, - selectAction = updateStartingScreen, - dismissAction = { resetDialog() } - ) - } - DialogState.LANGUAGE -> { AlertDialogWithList( title = stringResource(R.string.language_setting), @@ -266,7 +252,7 @@ private fun SettingsScreen( * All possible states for alert dialog that opens when user clicks on settings. */ private enum class DialogState { - NONE, START_SCREEN, LANGUAGE + NONE, LANGUAGE } @Preview @@ -274,10 +260,9 @@ private enum class DialogState { private fun PreviewSettingsScreen() { SettingsScreen( userPrefs = GeneralPreferences(), - menuButtonClick = { /*TODO*/ }, + navigateUp = {}, navControllerAction = {}, updateMiddleZero = {}, updateVibrations = {}, - updateStartingScreen = {} ) } 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 962a05b6..ef38efc7 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 @@ -47,11 +47,4 @@ internal class SettingsViewModel @Inject constructor( fun updateMiddleZero(enabled: Boolean) = viewModelScope.launch { userPrefsRepository.updateMiddleZero(enabled) } - - /** - * @see UserPreferencesRepository.updateStartingScreen - */ - fun updateStartingScreen(startingScreen: String) = viewModelScope.launch { - userPrefsRepository.updateStartingScreen(startingScreen) - } } diff --git a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/navigation/SettingsNavigation.kt b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/navigation/SettingsNavigation.kt index 2b75343b..beb6ad86 100644 --- a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/navigation/SettingsNavigation.kt +++ b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/navigation/SettingsNavigation.kt @@ -30,6 +30,7 @@ import com.sadellie.unitto.feature.settings.about.AboutScreen import com.sadellie.unitto.feature.settings.calculator.CalculatorSettingsScreen import com.sadellie.unitto.feature.settings.converter.ConverterSettingsScreen import com.sadellie.unitto.feature.settings.formatting.FormattingRoute +import com.sadellie.unitto.feature.settings.startingscreen.StartingScreenRoute import com.sadellie.unitto.feature.settings.themes.ThemesRoute import com.sadellie.unitto.feature.settings.thirdparty.ThirdPartyLicensesScreen import com.sadellie.unitto.feature.settings.unitgroups.UnitGroupsScreen @@ -38,6 +39,7 @@ import io.github.sadellie.themmo.ThemmoController private val graph = TopLevelDestinations.Settings.graph private val start = TopLevelDestinations.Settings.start internal const val themesRoute = "themes_route" +internal const val startingScreenRoute = "starting_screen_route" internal const val unitsGroupRoute = "units_group_route" internal const val thirdPartyRoute = "third_party_route" internal const val aboutRoute = "about_route" @@ -66,7 +68,7 @@ fun NavGraphBuilder.settingGraph( ) { unittoComposable(start) { SettingsRoute( - menuButtonClick = navController::navigateUp, + navigateUp = navController::navigateUp, navControllerAction = navController::navigate ) } @@ -78,6 +80,12 @@ fun NavGraphBuilder.settingGraph( ) } + unittoComposable(startingScreenRoute) { + StartingScreenRoute( + navigateUp = navController::navigateUp, + ) + } + unittoComposable(formattingRoute) { FormattingRoute( navigateUpAction = navController::navigateUp diff --git a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/startingscreen/StartingScreenScreen.kt b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/startingscreen/StartingScreenScreen.kt new file mode 100644 index 00000000..e0983403 --- /dev/null +++ b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/startingscreen/StartingScreenScreen.kt @@ -0,0 +1,120 @@ +/* + * Unitto is a unit converter for Android + * Copyright (c) 2023 Elshan Agaev + * + * 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 com.sadellie.unitto.feature.settings.startingscreen + +import android.os.Build +import androidx.compose.foundation.clickable +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.AppShortcut +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.RadioButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.sadellie.unitto.core.base.R +import com.sadellie.unitto.core.base.Shortcut +import com.sadellie.unitto.core.base.TOP_LEVEL_DESTINATIONS +import com.sadellie.unitto.core.base.TopLevelDestinations +import com.sadellie.unitto.core.ui.addShortcut +import com.sadellie.unitto.core.ui.common.NavigateUpButton +import com.sadellie.unitto.core.ui.common.UnittoListItem +import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar + +@Composable +internal fun StartingScreenRoute( + viewModel: StartingScreenViewModel = hiltViewModel(), + navigateUp: () -> Unit +) { + val prefs = viewModel.prefs.collectAsStateWithLifecycle() + + StartingScreenScreen( + startingScreen = prefs.value.startingScreen, + updateStartingScreen = viewModel::updateStartingScreen, + navigateUp = navigateUp + ) +} + +@Composable +private fun StartingScreenScreen( + startingScreen: String, + updateStartingScreen: (String) -> Unit, + navigateUp: () -> Unit +) { + val mContext = LocalContext.current + + UnittoScreenWithLargeTopBar( + title = stringResource(R.string.starting_screen_setting), + navigationIcon = { NavigateUpButton(navigateUp) } + ) { padding -> + LazyColumn(contentPadding = padding) { + + TOP_LEVEL_DESTINATIONS.forEach { destination -> + item { + UnittoListItem( + modifier = Modifier.clickable { updateStartingScreen(destination.graph) }, + headlineContent = { + Text(stringResource(destination.name)) + }, + leadingContent = { + RadioButton( + selected = destination.graph == startingScreen, + onClick = { updateStartingScreen(destination.graph) } + ) + }, + trailingContent = { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { + IconButton( + onClick = { + destination.shortcut?.let { shortcut: Shortcut -> + mContext.addShortcut( + destination.graph, + shortcut.shortcutShortLabel, + shortcut.shortcutLongLabel, + shortcut.shortcutDrawable + ) + } + } + ) { + Icon(Icons.Default.AppShortcut, null) + } + } + } + ) + } + } + } + } +} + +@Preview +@Composable +private fun StartingScreenPreview() { + StartingScreenScreen( + startingScreen = TopLevelDestinations.Converter.graph, + updateStartingScreen = {}, + navigateUp = {} + ) +} diff --git a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/startingscreen/StartingScreenViewModel.kt b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/startingscreen/StartingScreenViewModel.kt new file mode 100644 index 00000000..45b61526 --- /dev/null +++ b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/startingscreen/StartingScreenViewModel.kt @@ -0,0 +1,40 @@ +/* + * Unitto is a unit converter for Android + * Copyright (c) 2023 Elshan Agaev + * + * 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 com.sadellie.unitto.feature.settings.startingscreen + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.sadellie.unitto.data.common.stateIn +import com.sadellie.unitto.data.userprefs.StartingScreenPreferences +import com.sadellie.unitto.data.userprefs.UserPreferencesRepository +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +internal class StartingScreenViewModel @Inject constructor( + private val userPrefsRepository: UserPreferencesRepository, +) : ViewModel() { + val prefs = userPrefsRepository.startingScreenPrefs + .stateIn(viewModelScope, StartingScreenPreferences()) + + fun updateStartingScreen(startingScreen: String) = viewModelScope.launch { + userPrefsRepository.updateStartingScreen(startingScreen) + } +}