Create shortcuts manually

This commit is contained in:
Sad Ellie 2023-09-21 14:53:17 +03:00
parent 93ca31a955
commit 83f092fb7c
10 changed files with 257 additions and 45 deletions

View File

@ -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

View File

@ -101,11 +101,12 @@ sealed class TopLevelDestinations(
}
// Shown in settings
val TOP_LEVEL_GRAPH_ROUTES: Map<String, Int> 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,
)
}

View File

@ -16,11 +16,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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)
}

View File

@ -85,7 +85,12 @@ fun UnittoListItem(
)
}
}
trailingContent?.invoke()
trailingContent?.let {
ProvideColor(
color = MaterialTheme.colorScheme.onSurfaceVariant,
content = it
)
}
}
}

View File

@ -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<Preferences>) {
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<GeneralPreferences> = 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<StartingScreenPreferences> = 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

View File

@ -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 = {}
)
}

View File

@ -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)
}
}

View File

@ -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

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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 = {}
)
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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)
}
}