🚀 Add "Press back twice to exit" option

Added "Press back twice to exit the app" option. When true, to exit the app you need to press back twice (which may prevent unwanted exit).
This commit is contained in:
Acclorite 2024-08-25 09:32:26 +03:00
parent fe77f7bf0c
commit 39f2f3e87e
9 changed files with 99 additions and 10 deletions

View file

@ -14,6 +14,7 @@ object DataStoreConstants {
val THEME_CONTRAST = stringPreferencesKey("theme_contrast") val THEME_CONTRAST = stringPreferencesKey("theme_contrast")
val SHOW_START_SCREEN = booleanPreferencesKey("guide") val SHOW_START_SCREEN = booleanPreferencesKey("guide")
val CHECK_FOR_UPDATES = booleanPreferencesKey("check_for_updates") val CHECK_FOR_UPDATES = booleanPreferencesKey("check_for_updates")
val DOUBLE_PRESS_EXIT = booleanPreferencesKey("double_press_exit")
// Reader settings // Reader settings
val DOUBLE_CLICK_TRANSLATION = booleanPreferencesKey("double_click_translation") val DOUBLE_CLICK_TRANSLATION = booleanPreferencesKey("double_click_translation")

View file

@ -29,4 +29,5 @@ sealed class MainEvent {
data class OnChangeBrowseSortOrderDescending(val bool: Boolean) : MainEvent() data class OnChangeBrowseSortOrderDescending(val bool: Boolean) : MainEvent()
data class OnChangeBrowseIncludedFilterItem(val item: String) : MainEvent() data class OnChangeBrowseIncludedFilterItem(val item: String) : MainEvent()
data class OnChangeTextAlignment(val alignment: String) : MainEvent() data class OnChangeTextAlignment(val alignment: String) : MainEvent()
data class OnChangeDoublePressExit(val bool: Boolean) : MainEvent()
} }

View file

@ -36,22 +36,29 @@ import java.util.Locale
@Keep @Keep
@Parcelize @Parcelize
data class MainState( data class MainState(
// General Settings
val language: String? = null, val language: String? = null,
val theme: Theme? = null, val theme: Theme? = null,
val darkTheme: DarkTheme? = null, val darkTheme: DarkTheme? = null,
val pureDark: PureDark? = null, val pureDark: PureDark? = null,
val themeContrast: ThemeContrast? = null, val themeContrast: ThemeContrast? = null,
val showStartScreen: Boolean? = null,
val checkForUpdates: Boolean? = null,
val doublePressExit: Boolean? = null,
// Reader Settings
val fontFamily: String? = null, val fontFamily: String? = null,
val isItalic: Boolean? = null, val isItalic: Boolean? = null,
val fontSize: Int? = null, val fontSize: Int? = null,
val lineHeight: Int? = null, val lineHeight: Int? = null,
val paragraphHeight: Int? = null, val paragraphHeight: Int? = null,
val paragraphIndentation: Boolean? = null, val paragraphIndentation: Boolean? = null,
val showStartScreen: Boolean? = null,
val checkForUpdates: Boolean? = null,
val sidePadding: Int? = null, val sidePadding: Int? = null,
val doubleClickTranslation: Boolean? = null, val doubleClickTranslation: Boolean? = null,
val fastColorPresetChange: Boolean? = null, val fastColorPresetChange: Boolean? = null,
val textAlignment: ReaderTextAlignment? = null,
// Browse Settings
val browseFilesStructure: BrowseFilesStructure? = null, val browseFilesStructure: BrowseFilesStructure? = null,
val browseLayout: BrowseLayout? = null, val browseLayout: BrowseLayout? = null,
val browseAutoGridSize: Boolean? = null, val browseAutoGridSize: Boolean? = null,
@ -60,7 +67,6 @@ data class MainState(
val browseSortOrder: BrowseSortOrder? = null, val browseSortOrder: BrowseSortOrder? = null,
val browseSortOrderDescending: Boolean? = null, val browseSortOrderDescending: Boolean? = null,
val browseIncludedFilterItems: List<String>? = null, val browseIncludedFilterItems: List<String>? = null,
val textAlignment: ReaderTextAlignment? = null,
) : Parcelable { ) : Parcelable {
companion object { companion object {
/** /**
@ -157,6 +163,9 @@ data class MainState(
val textAlignment = data[TEXT_ALIGNMENT.name] as? String val textAlignment = data[TEXT_ALIGNMENT.name] as? String
?: ReaderTextAlignment.START.name ?: ReaderTextAlignment.START.name
val doublePressExit = data[DOUBLE_PRESS_EXIT.name] as? Boolean
?: false
return MainState( return MainState(
language = language, language = language,
theme = theme.toTheme(), theme = theme.toTheme(),
@ -182,7 +191,8 @@ data class MainState(
browseSortOrder = browseSortOrder.toBrowseSortOrder(), browseSortOrder = browseSortOrder.toBrowseSortOrder(),
browseSortOrderDescending = browseSortOrderDescending, browseSortOrderDescending = browseSortOrderDescending,
browseIncludedFilterItems = browseIncludedFilterItems, browseIncludedFilterItems = browseIncludedFilterItems,
textAlignment = textAlignment.toTextAlignment() textAlignment = textAlignment.toTextAlignment(),
doublePressExit = doublePressExit
) )
} }
} }

View file

@ -345,6 +345,20 @@ class MainViewModel @Inject constructor(
} }
} }
} }
is MainEvent.OnChangeDoublePressExit -> {
viewModelScope.launch(Dispatchers.IO) {
setDatastore.execute(
DataStoreConstants.DOUBLE_PRESS_EXIT,
event.bool
)
updateStateWithSavedHandle {
it.copy(
doublePressExit = event.bool
)
}
}
}
} }
} }

View file

@ -34,6 +34,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -54,6 +55,8 @@ import ua.acclorite.book_story.presentation.components.customItems
import ua.acclorite.book_story.presentation.components.header import ua.acclorite.book_story.presentation.components.header
import ua.acclorite.book_story.presentation.components.is_messages.IsEmpty import ua.acclorite.book_story.presentation.components.is_messages.IsEmpty
import ua.acclorite.book_story.presentation.data.LocalNavigator import ua.acclorite.book_story.presentation.data.LocalNavigator
import ua.acclorite.book_story.presentation.data.MainState
import ua.acclorite.book_story.presentation.data.MainViewModel
import ua.acclorite.book_story.presentation.data.Screen import ua.acclorite.book_story.presentation.data.Screen
import ua.acclorite.book_story.presentation.screens.browse.data.BrowseEvent import ua.acclorite.book_story.presentation.screens.browse.data.BrowseEvent
import ua.acclorite.book_story.presentation.screens.browse.data.BrowseViewModel import ua.acclorite.book_story.presentation.screens.browse.data.BrowseViewModel
@ -73,10 +76,12 @@ import ua.acclorite.book_story.presentation.ui.Transitions
fun LibraryScreenRoot() { fun LibraryScreenRoot() {
val navigator = LocalNavigator.current val navigator = LocalNavigator.current
val viewModel: LibraryViewModel = hiltViewModel() val viewModel: LibraryViewModel = hiltViewModel()
val mainViewModel: MainViewModel = hiltViewModel()
val browseViewModel: BrowseViewModel = hiltViewModel() val browseViewModel: BrowseViewModel = hiltViewModel()
val historyViewModel: HistoryViewModel = hiltViewModel() val historyViewModel: HistoryViewModel = hiltViewModel()
val state = viewModel.state.collectAsState() val state = viewModel.state.collectAsState()
val mainState = mainViewModel.state.collectAsState()
val pagerState = rememberPagerState( val pagerState = rememberPagerState(
initialPage = state.value.currentPage, initialPage = state.value.currentPage,
@ -100,6 +105,7 @@ fun LibraryScreenRoot() {
LibraryScreen( LibraryScreen(
state = state, state = state,
mainState = mainState,
onNavigate = { navigator.it() }, onNavigate = { navigator.it() },
pagerState = pagerState, pagerState = pagerState,
refreshState = refreshState, refreshState = refreshState,
@ -113,6 +119,7 @@ fun LibraryScreenRoot() {
@Composable @Composable
private fun LibraryScreen( private fun LibraryScreen(
state: State<LibraryState>, state: State<LibraryState>,
mainState: State<MainState>,
onNavigate: OnNavigate, onNavigate: OnNavigate,
pagerState: PagerState, pagerState: PagerState,
refreshState: PullRefreshState, refreshState: PullRefreshState,
@ -264,7 +271,7 @@ private fun LibraryScreen(
val activity = LocalContext.current as ComponentActivity val activity = LocalContext.current as ComponentActivity
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
var shouldExit = remember { false } var shouldExit = rememberSaveable { false }
BackHandler { BackHandler {
if (state.value.hasSelectedItems) { if (state.value.hasSelectedItems) {
onEvent(LibraryEvent.OnClearSelectedBooks) onEvent(LibraryEvent.OnClearSelectedBooks)
@ -281,7 +288,7 @@ private fun LibraryScreen(
return@BackHandler return@BackHandler
} }
if (shouldExit) { if (shouldExit || !mainState.value.doublePressExit!!) {
activity.finish() activity.finish()
return@BackHandler return@BackHandler
} }

View file

@ -15,6 +15,7 @@ import ua.acclorite.book_story.presentation.data.MainState
import ua.acclorite.book_story.presentation.screens.settings.data.SettingsEvent import ua.acclorite.book_story.presentation.screens.settings.data.SettingsEvent
import ua.acclorite.book_story.presentation.screens.settings.nested.general.components.settings.AppLanguageSetting import ua.acclorite.book_story.presentation.screens.settings.nested.general.components.settings.AppLanguageSetting
import ua.acclorite.book_story.presentation.screens.settings.nested.general.components.settings.CheckForUpdatesSetting import ua.acclorite.book_story.presentation.screens.settings.nested.general.components.settings.CheckForUpdatesSetting
import ua.acclorite.book_story.presentation.screens.settings.nested.general.components.settings.DoublePressExitSetting
/** /**
* General Settings Category. * General Settings Category.
@ -56,6 +57,13 @@ fun LazyListScope.GeneralSettingsCategory(
) )
} }
item {
DoublePressExitSetting(
state = state,
onMainEvent = onMainEvent
)
}
item { item {
Spacer( Spacer(
modifier = Modifier modifier = Modifier

View file

@ -0,0 +1,34 @@
package ua.acclorite.book_story.presentation.screens.settings.nested.general.components.settings
import android.annotation.SuppressLint
import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainState
import ua.acclorite.book_story.presentation.screens.settings.components.SwitchWithTitle
/**
* Double Press Exit setting.
* If true, to close the app you need to press back twice.
*/
@SuppressLint("InlinedApi")
@Composable
fun LazyItemScope.DoublePressExitSetting(
state: State<MainState>,
onMainEvent: (MainEvent) -> Unit
) {
SwitchWithTitle(
selected = state.value.doublePressExit!!,
modifier = Modifier.animateItem(),
title = stringResource(id = R.string.double_press_exit_option),
description = stringResource(id = R.string.double_press_exit_option_desc)
) {
onMainEvent(
MainEvent.OnChangeDoublePressExit(!state.value.doublePressExit!!)
)
}
}

View file

@ -211,11 +211,18 @@
<string name="sort_browse_settings">Сотрування</string> <string name="sort_browse_settings">Сотрування</string>
<!-- Settings options --> <!-- Settings options -->
<!-- General -->
<string name="language_option">Мова застосунка</string> <string name="language_option">Мова застосунка</string>
<string name="dark_theme_option">Темна тема</string> <string name="dark_theme_option">Темна тема</string>
<string name="pure_dark_option">Суцільна темрява (ОЛЕД)</string> <string name="pure_dark_option">Суцільна темрява (ОЛЕД)</string>
<string name="theme_contrast_option">Контрастність теми</string> <string name="theme_contrast_option">Контрастність теми</string>
<string name="app_theme_option">Тема застосунка</string> <string name="app_theme_option">Тема застосунка</string>
<string name="check_for_updates_option">Перевіряти на оновлення</string>
<string name="check_for_updates_option_desc">Перевіряти на новішу версію застосунку при запуску</string>
<string name="double_press_exit_option">Подвійне натискання для виходу</string>
<string name="double_press_exit_option_desc">Натисніть назад двічі, щоб вийти з застосунку</string>
<!-- Reader -->
<string name="font_family_option">Сімейство шрифтів</string> <string name="font_family_option">Сімейство шрифтів</string>
<string name="font_style_option">Стиль шрифта</string> <string name="font_style_option">Стиль шрифта</string>
<string name="font_size_option">Розмір шрифта</string> <string name="font_size_option">Розмір шрифта</string>
@ -225,14 +232,14 @@
<string name="color_preset_option">Пресет кольорів</string> <string name="color_preset_option">Пресет кольорів</string>
<string name="background_color_option">Колір фона</string> <string name="background_color_option">Колір фона</string>
<string name="font_color_option">Колір шрифта</string> <string name="font_color_option">Колір шрифта</string>
<string name="check_for_updates_option">Перевіряти на оновлення</string>
<string name="check_for_updates_option_desc">Перевіряти на новішу версію застосунку при запуску</string>
<string name="side_padding_option">Бокові відступи</string> <string name="side_padding_option">Бокові відступи</string>
<string name="double_click_translation_option">Переклад подвійним кліком</string> <string name="double_click_translation_option">Переклад подвійним кліком</string>
<string name="double_click_translation_option_desc">Двічі клацніть на абзац, щоб перекласти його</string> <string name="double_click_translation_option_desc">Двічі клацніть на абзац, щоб перекласти його</string>
<string name="fast_color_preset_change_option">Швидка зміна пресета кольорів</string> <string name="fast_color_preset_change_option">Швидка зміна пресета кольорів</string>
<string name="fast_color_preset_change_option_desc">Проведіть вліво або вправо, щоб змінити пресет кольорів</string> <string name="fast_color_preset_change_option_desc">Проведіть вліво або вправо, щоб змінити пресет кольорів</string>
<string name="text_alignment_option">Розташування текста</string> <string name="text_alignment_option">Розташування текста</string>
<!-- Browse -->
<string name="browse_files_structure_option">Структура файлів</string> <string name="browse_files_structure_option">Структура файлів</string>
<string name="browse_layout_option">Режим відображення</string> <string name="browse_layout_option">Режим відображення</string>
<string name="browse_grid_size_option">Кількість комірок</string> <string name="browse_grid_size_option">Кількість комірок</string>

View file

@ -230,11 +230,18 @@
<string name="sort_browse_settings">Sort</string> <string name="sort_browse_settings">Sort</string>
<!-- Settings options --> <!-- Settings options -->
<!-- General -->
<string name="language_option">App language</string> <string name="language_option">App language</string>
<string name="dark_theme_option">Dark theme</string> <string name="dark_theme_option">Dark theme</string>
<string name="pure_dark_option">Pure dark (OLED)</string> <string name="pure_dark_option">Pure dark (OLED)</string>
<string name="theme_contrast_option">Theme contrast</string> <string name="theme_contrast_option">Theme contrast</string>
<string name="app_theme_option">App theme</string> <string name="app_theme_option">App theme</string>
<string name="check_for_updates_option">Check for updates</string>
<string name="check_for_updates_option_desc">Check for newer app version on startup</string>
<string name="double_press_exit_option">Double back press to exit</string>
<string name="double_press_exit_option_desc">Press back twice to exit the app</string>
<!-- Reader -->
<string name="font_family_option">Font family</string> <string name="font_family_option">Font family</string>
<string name="font_style_option">Font style</string> <string name="font_style_option">Font style</string>
<string name="font_size_option">Font size</string> <string name="font_size_option">Font size</string>
@ -244,14 +251,14 @@
<string name="color_preset_option">Color preset</string> <string name="color_preset_option">Color preset</string>
<string name="background_color_option">Background color</string> <string name="background_color_option">Background color</string>
<string name="font_color_option">Font color</string> <string name="font_color_option">Font color</string>
<string name="check_for_updates_option">Check for updates</string>
<string name="check_for_updates_option_desc">Check for newer app version on startup</string>
<string name="side_padding_option">Side padding</string> <string name="side_padding_option">Side padding</string>
<string name="double_click_translation_option">Double click translation</string> <string name="double_click_translation_option">Double click translation</string>
<string name="double_click_translation_option_desc">Double click on paragraph to translate it</string> <string name="double_click_translation_option_desc">Double click on paragraph to translate it</string>
<string name="fast_color_preset_change_option">Fast color preset change</string> <string name="fast_color_preset_change_option">Fast color preset change</string>
<string name="fast_color_preset_change_option_desc">Swipe left or right to change color preset</string> <string name="fast_color_preset_change_option_desc">Swipe left or right to change color preset</string>
<string name="text_alignment_option">Text alignment</string> <string name="text_alignment_option">Text alignment</string>
<!-- Browse -->
<string name="browse_files_structure_option">Files structure</string> <string name="browse_files_structure_option">Files structure</string>
<string name="browse_layout_option">Display mode</string> <string name="browse_layout_option">Display mode</string>
<string name="browse_grid_size_option">Grid size</string> <string name="browse_grid_size_option">Grid size</string>