diff --git a/app/src/keyboards/java/be/scri/services/GeneralKeyboardIME.kt b/app/src/keyboards/java/be/scri/services/GeneralKeyboardIME.kt index a53ff457..1b8b6afb 100644 --- a/app/src/keyboards/java/be/scri/services/GeneralKeyboardIME.kt +++ b/app/src/keyboards/java/be/scri/services/GeneralKeyboardIME.kt @@ -594,8 +594,6 @@ abstract class GeneralKeyboardIME( return isActionSearch || isUriType || hasSearchHint } - override fun isClipboardKeyEnabled(): Boolean = PreferencesHelper.getIsClipboardKeyEnabled(this, language) - private fun loadLanguageData() { val languageAlias = getLanguageAlias(language) dataContract = dbManagers.getLanguageContract(languageAlias) diff --git a/app/src/main/assets/i18n b/app/src/main/assets/i18n index 58b1dd61..fe9fa190 160000 --- a/app/src/main/assets/i18n +++ b/app/src/main/assets/i18n @@ -1 +1 @@ -Subproject commit 58b1dd6190e0e43d9892980b52150449861838ab +Subproject commit fe9fa1901a12a14d71fec283b0afe5e702398a28 diff --git a/app/src/main/java/be/scri/App.kt b/app/src/main/java/be/scri/App.kt index 7b5430be..7e9b2081 100644 --- a/app/src/main/java/be/scri/App.kt +++ b/app/src/main/java/be/scri/App.kt @@ -133,302 +133,336 @@ fun ScribeApp( cancelCheckForNewData = conjugateDownloadViewModel::cancelCheckForNewData, ) + val downloadToastMessage by downloadViewModel.toastMessage.collectAsState() + val conjugateToastMessage by conjugateDownloadViewModel.toastMessage.collectAsState() + val activeToastMessage = downloadToastMessage ?: conjugateToastMessage + val isOfflineErrorVisible = activeToastMessage != null + val screens = remember(context) { BottomBarScreen.getScreens() } ScribeTheme( useDarkTheme = isDarkTheme, isIncreaseTextSize = isIncreaseTextSize, ) { - Scaffold( - bottomBar = { - ScribeBottomBar( - onItemClick = { - coroutineScope.launch { - if (navBackStackEntry?.destination?.route != "pager") { - navController.popBackStack() + Box(modifier = modifier.fillMaxSize()) { + Scaffold( + bottomBar = { + ScribeBottomBar( + onItemClick = { + coroutineScope.launch { + if (navBackStackEntry?.destination?.route != "pager") { + navController.popBackStack() + } + pagerState.animateScrollToPage(it) } - pagerState.animateScrollToPage(it) - } - }, - pagerState = pagerState, - modifier = Modifier, - screens = screens, - ) - }, - modifier = modifier.fillMaxSize(), - ) { innerPadding -> - NavHost( - navController = navController, - startDestination = "pager", - ) { - composable("pager") { - HorizontalPager( - state = pagerState, - beyondViewportPageCount = screens.size, - modifier = Modifier.padding(innerPadding), - ) { page -> - when (screens[page]) { - is BottomBarScreen.Installation -> { - Box( - modifier = Modifier.fillMaxSize(), - ) { - InstallationScreen( - isDark = isDarkTheme, - context = context, - onNavigateToDownloadData = { - navController.navigate("download_data") - }, - onTutorialClick = { - navController.navigate("tutorial") + }, + pagerState = pagerState, + modifier = Modifier, + screens = screens, + ) + }, + modifier = Modifier.fillMaxSize(), + ) { innerPadding -> + NavHost( + navController = navController, + startDestination = "pager", + ) { + composable("pager") { + HorizontalPager( + state = pagerState, + beyondViewportPageCount = screens.size, + modifier = Modifier.padding(innerPadding), + ) { page -> + when (screens[page]) { + is BottomBarScreen.Installation -> { + Box( + modifier = Modifier.fillMaxSize(), + ) { + InstallationScreen( + isDark = isDarkTheme, + context = context, + onNavigateToDownloadData = { + navController.navigate("download_data") + }, + onTutorialClick = { + navController.navigate("tutorial") - coroutineScope.launch { - kotlinx.coroutines.delay(400) - val aboutIndex = screens.indexOfFirst { it is BottomBarScreen.About } - if (aboutIndex != -1) { - pagerState.scrollToPage(aboutIndex) + coroutineScope.launch { + kotlinx.coroutines.delay(400) + val aboutIndex = screens.indexOfFirst { it is BottomBarScreen.About } + if (aboutIndex != -1) { + pagerState.scrollToPage(aboutIndex) + } } - } - }, - ) - HintDialog( - pagerState = pagerState, - currentPageIndex = page, - sharedPrefsKey = "hint_shown_main", - hintMessageResId = R.string.i18n_app_installation_app_hint_tooltip, - isHintChanged = isHintChanged[page] == true, - onDismiss = { onDismiss(it) }, - modifier = - Modifier - .fillMaxWidth() - .padding(8.dp), - ) + }, + ) + HintDialog( + pagerState = pagerState, + currentPageIndex = page, + sharedPrefsKey = "hint_shown_main", + hintMessageResId = R.string.i18n_app_installation_app_hint_tooltip, + isHintChanged = isHintChanged[page] == true, + onDismiss = { onDismiss(it) }, + modifier = + Modifier + .fillMaxWidth() + .padding(8.dp), + ) + } + HandleBackPress(pagerState, coroutineScope) } - HandleBackPress(pagerState, coroutineScope) - } - is BottomBarScreen.Conjugate -> { - Box( - modifier = Modifier.fillMaxSize(), - ) { - ConjugateScreen( - onNavigateToDownloadData = { - navController.navigate("conjugate_download_data") - }, - onNavigateToConjugationSelection = { verb, languageAlias -> - navController.navigate( - "${Screen.ConjugationSelection.route}/$verb/$languageAlias", - ) - }, - ) - HintDialog( - pagerState = pagerState, - currentPageIndex = page, - sharedPrefsKey = "hint_shown_conjugate", - hintMessageResId = R.string.i18n_app_conjugate_app_hint_tooltip, - isHintChanged = isHintChanged[page] == true, - onDismiss = { onDismiss(it) }, - modifier = - Modifier - .fillMaxWidth() - .padding(8.dp), - ) + is BottomBarScreen.Conjugate -> { + Box( + modifier = Modifier.fillMaxSize(), + ) { + ConjugateScreen( + onNavigateToDownloadData = { + navController.navigate("conjugate_download_data") + }, + onNavigateToConjugationSelection = { verb, languageAlias -> + navController.navigate( + "${Screen.ConjugationSelection.route}/$verb/$languageAlias", + ) + }, + ) + HintDialog( + pagerState = pagerState, + currentPageIndex = page, + sharedPrefsKey = "hint_shown_conjugate", + hintMessageResId = R.string.i18n_app_conjugate_app_hint_tooltip, + isHintChanged = isHintChanged[page] == true, + onDismiss = { onDismiss(it) }, + modifier = + Modifier + .fillMaxWidth() + .padding(8.dp), + ) + } + HandleBackPress(pagerState, coroutineScope) } - HandleBackPress(pagerState, coroutineScope) - } - is BottomBarScreen.Settings -> { - Box( - modifier = Modifier.fillMaxSize(), - ) { - SettingsScreen( - onDarkModeChange = { isDarkMode -> - onDarkModeChange(isDarkMode) - }, - onIncreaseTextSizeChange = { increaseTextSize -> - onIncreaseTextSizeChange(increaseTextSize) - }, - onLanguageSettingsClick = { language -> - navController.navigate( - "${Screen.LanguageSettings.route}/$language", - ) - }, - context = context, - ) - HintDialog( - pagerState = pagerState, - currentPageIndex = page, - sharedPrefsKey = "hint_shown_settings", - hintMessageResId = - if (FlavorProvider.get() == AppFlavor.CONJUGATE) { - R.string.i18n_app_settings_conjugate_app_hint_tooltip - } else { - R.string.i18n_app_settings_keyboard_app_hint_tooltip + is BottomBarScreen.Settings -> { + Box( + modifier = Modifier.fillMaxSize(), + ) { + SettingsScreen( + onDarkModeChange = { isDarkMode -> + onDarkModeChange(isDarkMode) }, - isHintChanged = isHintChanged[page] == true, - onDismiss = { onDismiss(it) }, - modifier = Modifier.padding(8.dp), - ) + onIncreaseTextSizeChange = { increaseTextSize -> + onIncreaseTextSizeChange(increaseTextSize) + }, + onLanguageSettingsClick = { language -> + navController.navigate( + "${Screen.LanguageSettings.route}/$language", + ) + }, + context = context, + ) + HintDialog( + pagerState = pagerState, + currentPageIndex = page, + sharedPrefsKey = "hint_shown_settings", + hintMessageResId = + if (FlavorProvider.get() == AppFlavor.CONJUGATE) { + R.string.i18n_app_settings_conjugate_app_hint_tooltip + } else { + R.string.i18n_app_settings_keyboard_app_hint_tooltip + }, + isHintChanged = isHintChanged[page] == true, + onDismiss = { onDismiss(it) }, + modifier = Modifier.padding(8.dp), + ) + } + HandleBackPress(pagerState, coroutineScope) } - HandleBackPress(pagerState, coroutineScope) - } - is BottomBarScreen.About -> { - Box( - modifier = Modifier.fillMaxSize(), - ) { - AboutScreen( - onPrivacyPolicyClick = { - navController.navigate(Screen.PrivacyPolicy.route) - }, - onThirdPartyLicensesClick = { - navController.navigate(Screen.ThirdParty.route) - }, - onWikiClick = { - navController.navigate(Screen.WikimediaScribe.route) - }, - onTutorialClick = { - navController.navigate("tutorial") - }, - resetHints = { resetHints() }, - context = context, - ) - HintDialog( - pagerState = pagerState, - currentPageIndex = page, - sharedPrefsKey = "hint_shown_about", - hintMessageResId = R.string.i18n_app_about_app_hint_tooltip, - isHintChanged = isHintChanged[page] == true, - onDismiss = { onDismiss(it) }, - modifier = Modifier.padding(8.dp), - ) + is BottomBarScreen.About -> { + Box( + modifier = Modifier.fillMaxSize(), + ) { + AboutScreen( + onPrivacyPolicyClick = { + navController.navigate(Screen.PrivacyPolicy.route) + }, + onThirdPartyLicensesClick = { + navController.navigate(Screen.ThirdParty.route) + }, + onWikiClick = { + navController.navigate(Screen.WikimediaScribe.route) + }, + onTutorialClick = { + navController.navigate("tutorial") + }, + resetHints = { resetHints() }, + context = context, + ) + HintDialog( + pagerState = pagerState, + currentPageIndex = page, + sharedPrefsKey = "hint_shown_about", + hintMessageResId = R.string.i18n_app_about_app_hint_tooltip, + isHintChanged = isHintChanged[page] == true, + onDismiss = { onDismiss(it) }, + modifier = Modifier.padding(8.dp), + ) + } + HandleBackPress(pagerState, coroutineScope) } - HandleBackPress(pagerState, coroutineScope) } } } - } - - composable("tutorial") { - TutorialNavigator( - onTutorialExit = { - navController.popBackStack() - }, - modifier = Modifier.padding(innerPadding), - ) - } - composable("download_data") { - DownloadDataScreen( - onBackNavigation = { - navController.popBackStack() - }, - onNavigateToTranslation = { language -> - navController.navigate( - "translation_language_detail/$language", - ) - }, - isDarkTheme = isDarkTheme, - downloadActions = downloadActions, - checkUpdateActions = checkUpdateActions, - modifier = Modifier.padding(innerPadding), - ) - } - - composable("conjugate_download_data") { - ConjugateDownloadDataScreen( - onBackNavigation = { - navController.popBackStack() - }, - isDarkTheme = isDarkTheme, - downloadActions = conjugateDownloadActions, - checkUpdateActions = conjugateCheckUpdateActions, - modifier = Modifier.padding(innerPadding), - ) - } + composable("tutorial") { + TutorialNavigator( + onTutorialExit = { + navController.popBackStack() + }, + modifier = Modifier.padding(innerPadding), + ) + } - composable("${Screen.ConjugationSelection.route}/{verb}/{languageAlias}") { backStackEntry -> - val verb = backStackEntry.arguments?.getString("verb") ?: "" - val languageAlias = backStackEntry.arguments?.getString("languageAlias") ?: "" - ConjugationSelectionScreen( - verb = verb, - languageAlias = languageAlias, - onBackNavigation = { navController.popBackStack() }, - modifier = Modifier.padding(innerPadding), - ) - } + composable("download_data") { + DownloadDataScreen( + onBackNavigation = { + navController.popBackStack() + }, + onNavigateToTranslation = { language -> + navController.navigate( + "translation_language_detail/$language", + ) + }, + isDarkTheme = isDarkTheme, + downloadActions = downloadActions, + checkUpdateActions = checkUpdateActions, + modifier = Modifier.padding(innerPadding), + ) + } - composable("${Screen.LanguageSettings.route}/{languageName}") { - val language = it.arguments?.getString("languageName") - if (language != null) { - LanguageSettingsScreen( - language = language, + composable("conjugate_download_data") { + ConjugateDownloadDataScreen( onBackNavigation = { navController.popBackStack() }, + isDarkTheme = isDarkTheme, + downloadActions = conjugateDownloadActions, + checkUpdateActions = conjugateCheckUpdateActions, + modifier = Modifier.padding(innerPadding), + ) + } + + composable("${Screen.ConjugationSelection.route}/{verb}/{languageAlias}") { backStackEntry -> + val verb = backStackEntry.arguments?.getString("verb") ?: "" + val languageAlias = backStackEntry.arguments?.getString("languageAlias") ?: "" + ConjugationSelectionScreen( + verb = verb, + languageAlias = languageAlias, + onBackNavigation = { navController.popBackStack() }, modifier = Modifier.padding(innerPadding), - onTranslationLanguageSelect = { - navController.navigate("translation_language_detail/$language") + ) + } + + composable("${Screen.LanguageSettings.route}/{languageName}") { + val language = it.arguments?.getString("languageName") + if (language != null) { + LanguageSettingsScreen( + language = language, + onBackNavigation = { + navController.popBackStack() + }, + modifier = Modifier.padding(innerPadding), + onTranslationLanguageSelect = { + navController.navigate("translation_language_detail/$language") + }, + onCurrencySelect = { + val currentSymbol = PreferencesHelper.getDefaultCurrencySymbol(context, language) + navController.navigate("currency_symbol_detail/$currentSymbol/$language") + }, + ) + } + } + + composable("translation_language_detail" + "/{languageName}") { backStackEntry -> + val language = backStackEntry.arguments?.getString("languageName") ?: "" + SelectTranslationSourceLanguageScreen( + onBackNavigation = { + navController.popBackStack() }, - onCurrencySelect = { - val currentSymbol = PreferencesHelper.getDefaultCurrencySymbol(context, language) - navController.navigate("currency_symbol_detail/$currentSymbol/$language") + onNavigateToDownloadData = { + navController.navigate("download_data") }, + onDownloadAction = onDownloadAction, + modifier = Modifier.padding(innerPadding), + currentLanguage = language, ) } - } - composable("translation_language_detail" + "/{languageName}") { backStackEntry -> - val language = backStackEntry.arguments?.getString("languageName") ?: "" - SelectTranslationSourceLanguageScreen( - onBackNavigation = { - navController.popBackStack() - }, - onNavigateToDownloadData = { - navController.navigate("download_data") - }, - onDownloadAction = onDownloadAction, - modifier = Modifier.padding(innerPadding), - currentLanguage = language, - ) - } + composable("currency_symbol_detail/{symbolName}/{languageName}") { backStackEntry -> + val language = backStackEntry.arguments?.getString("languageName") ?: "" + DefaultCurrencySymbolScreen( + currentLanguage = language, + onBackNavigation = { + navController.popBackStack() + }, + modifier = Modifier.padding(innerPadding), + ) + } - composable("currency_symbol_detail/{symbolName}/{languageName}") { backStackEntry -> - val language = backStackEntry.arguments?.getString("languageName") ?: "" - DefaultCurrencySymbolScreen( - currentLanguage = language, - onBackNavigation = { - navController.popBackStack() - }, - modifier = Modifier.padding(innerPadding), - ) - } + composable(Screen.WikimediaScribe.route) { + WikimediaScreen( + onBackNavigation = { + navController.popBackStack() + }, + modifier = Modifier.padding(innerPadding), + ) + } - composable(Screen.WikimediaScribe.route) { - WikimediaScreen( - onBackNavigation = { - navController.popBackStack() - }, - modifier = Modifier.padding(innerPadding), - ) - } + composable(Screen.PrivacyPolicy.route) { + PrivacyPolicyScreen( + onBackNavigation = { + navController.popBackStack() + }, + modifier = Modifier.padding(innerPadding), + ) + } - composable(Screen.PrivacyPolicy.route) { - PrivacyPolicyScreen( - onBackNavigation = { - navController.popBackStack() - }, - modifier = Modifier.padding(innerPadding), - ) - } + composable(Screen.ThirdParty.route) { + ThirdPartyScreen( + onBackNavigation = { + navController.popBackStack() + }, + modifier = Modifier.padding(innerPadding), + ) + } + } // Ends NavHost + } // Ends Scaffold content - composable(Screen.ThirdParty.route) { - ThirdPartyScreen( - onBackNavigation = { - navController.popBackStack() - }, - modifier = Modifier.padding(innerPadding), + androidx.compose.animation.AnimatedVisibility( + visible = isOfflineErrorVisible, + enter = androidx.compose.animation.slideInVertically(initialOffsetY = { it }) + androidx.compose.animation.fadeIn(), + exit = androidx.compose.animation.slideOutVertically(targetOffsetY = { it }) + androidx.compose.animation.fadeOut(), + modifier = + Modifier + .align(androidx.compose.ui.Alignment.BottomCenter) + .fillMaxWidth() + .padding(bottom = 120.dp, start = 16.dp, end = 16.dp), + ) { + androidx.compose.material3.Surface( + shape = + androidx.compose.foundation.shape + .RoundedCornerShape(12.dp), + color = androidx.compose.ui.graphics.Color.White, + shadowElevation = 8.dp, + ) { + androidx.compose.material3.Text( + text = activeToastMessage ?: "", + color = + androidx.compose.ui.graphics + .Color(0xFFE68A00), + modifier = Modifier.padding(16.dp), ) } } - } - } -} + } // Ends Box + } // Ends ScribeTheme +} // Ends App /** * Handles the back press behavior within the pager. diff --git a/app/src/main/java/be/scri/helpers/KeyboardBase.kt b/app/src/main/java/be/scri/helpers/KeyboardBase.kt index 06fbe0b7..f5c89fdf 100644 --- a/app/src/main/java/be/scri/helpers/KeyboardBase.kt +++ b/app/src/main/java/be/scri/helpers/KeyboardBase.kt @@ -37,8 +37,6 @@ class KeyboardBase { val keyboardLetters: Int fun isSearchBar(): Boolean - - fun isClipboardKeyEnabled(): Boolean } /** Horizontal gap default for all rows */ diff --git a/app/src/main/java/be/scri/helpers/NetworkMonitor.kt b/app/src/main/java/be/scri/helpers/NetworkMonitor.kt new file mode 100644 index 00000000..ca7ca6d7 --- /dev/null +++ b/app/src/main/java/be/scri/helpers/NetworkMonitor.kt @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +package be.scri.helpers + +import android.content.Context +import android.net.ConnectivityManager +import android.net.NetworkCapabilities + +/** + * Utility singleton to check network connectivity status. + */ +object NetworkMonitor { + /** + * Checks if the device is currently connected to the internet. + * + * @param context The application context. + * @return true if connected via WiFi, Cellular, or Ethernet, false otherwise. + */ + fun isOnline(context: Context): Boolean { + val connectivityManager = + context.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager + ?: return false + + val activeNetwork = connectivityManager.activeNetwork ?: return false + val capabilities = connectivityManager.getNetworkCapabilities(activeNetwork) ?: return false + + return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) && + capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) + } +} diff --git a/app/src/main/java/be/scri/helpers/PreferencesHelper.kt b/app/src/main/java/be/scri/helpers/PreferencesHelper.kt index 88284f49..b449b320 100644 --- a/app/src/main/java/be/scri/helpers/PreferencesHelper.kt +++ b/app/src/main/java/be/scri/helpers/PreferencesHelper.kt @@ -26,7 +26,6 @@ object PreferencesHelper { private const val DEFAULT_CURRENCY = "default_currency" private const val HOLD_FOR_ALT_KEYS = "hold_for_alt_keys" private const val INCREASE_TEXT_SIZE = "increase_text_size" - private const val CLIPBOARD_KEY_ON_KEYBOARD = "clipboard_key_on_keyboard" /** * Sets the translation source language for a given language. @@ -535,31 +534,6 @@ object PreferencesHelper { return sharedPref.getBoolean(getLanguageSpecificPreferenceKey(HOLD_FOR_ALT_KEYS, language), true) } - /** - * Sets the preference for showing or hiding the clipboard key on the keyboard. - */ - fun setClipboardKeyPreference( - context: Context, - language: String, - enabled: Boolean, - ) { - val sharedPref = context.getSharedPreferences(SCRIBE_PREFS, Context.MODE_PRIVATE) - sharedPref.edit { - putBoolean(getLanguageSpecificPreferenceKey(CLIPBOARD_KEY_ON_KEYBOARD, language), enabled) - } - } - - /** - * Retrieves whether the clipboard key is enabled on the keyboard for a given language. - */ - fun getIsClipboardKeyEnabled( - context: Context, - language: String, - ): Boolean { - val sharedPref = context.getSharedPreferences(SCRIBE_PREFS, Context.MODE_PRIVATE) - return sharedPref.getBoolean(getLanguageSpecificPreferenceKey(CLIPBOARD_KEY_ON_KEYBOARD, language), true) - } - /** * Resets the application hints, marking them as not shown in the shared preferences. * diff --git a/app/src/main/java/be/scri/ui/screens/LanguageSettingsScreen.kt b/app/src/main/java/be/scri/ui/screens/LanguageSettingsScreen.kt index 887dcfe7..82d55761 100644 --- a/app/src/main/java/be/scri/ui/screens/LanguageSettingsScreen.kt +++ b/app/src/main/java/be/scri/ui/screens/LanguageSettingsScreen.kt @@ -109,13 +109,6 @@ fun LanguageSettingsScreen( ) } - val clipboardKeyOnKeyboardState = - remember { - mutableStateOf( - PreferencesHelper.getIsClipboardKeyEnabled(context, language), - ) - } - val wordByWordDeletionState = remember { mutableStateOf( @@ -161,15 +154,6 @@ fun LanguageSettingsScreen( shouldDisableAccentCharacter, ) }, - toggleClipboardKeyOnKeyboard = clipboardKeyOnKeyboardState.value, - onToggleClipboardKeyOnKeyboard = { isEnabled -> - clipboardKeyOnKeyboardState.value = isEnabled - PreferencesHelper.setClipboardKeyPreference( - context, - language, - isEnabled, - ) - }, onCurrencySelect = onCurrencySelect, ), ) @@ -366,8 +350,6 @@ private fun getLayoutListData( onTogglePeriodAndComma: (Boolean) -> Unit, toggleDisableAccentCharacter: Boolean, onToggleDisableAccentCharacter: (Boolean) -> Unit, - toggleClipboardKeyOnKeyboard: Boolean, - onToggleClipboardKeyOnKeyboard: (Boolean) -> Unit, onCurrencySelect: () -> Unit, ): List { val list: MutableList = mutableListOf() @@ -393,14 +375,7 @@ private fun getLayoutListData( onToggle = onTogglePeriodAndComma, ), ) - list.add( - ScribeItem.SwitchItem( - title = R.string.i18n_app_settings_keyboard_layout_clipboard_on_keyboard, - desc = R.string.i18n_app_settings_keyboard_layout_clipboard_on_keyboard_description, - state = toggleClipboardKeyOnKeyboard, - onToggle = onToggleClipboardKeyOnKeyboard, - ), - ) + list.add( ScribeItem.ClickableItem( title = R.string.i18n_app_settings_keyboard_layout_default_currency, diff --git a/app/src/main/java/be/scri/ui/screens/download/ConjugateDataDownloadViewModel.kt b/app/src/main/java/be/scri/ui/screens/download/ConjugateDataDownloadViewModel.kt index 637e3d43..9e596f91 100644 --- a/app/src/main/java/be/scri/ui/screens/download/ConjugateDataDownloadViewModel.kt +++ b/app/src/main/java/be/scri/ui/screens/download/ConjugateDataDownloadViewModel.kt @@ -6,7 +6,6 @@ import android.app.Application import android.content.Context import android.database.sqlite.SQLiteException import android.util.Log -import android.widget.Toast import androidx.compose.runtime.mutableStateMapOf import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope @@ -14,6 +13,7 @@ import be.scri.R import be.scri.data.remote.ConjugateDynamicDbHelper import be.scri.data.remote.RetrofitClient import be.scri.helpers.LanguageMappingConstants +import be.scri.helpers.NetworkMonitor import be.scri.helpers.StringUtils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -29,6 +29,7 @@ import java.io.IOException import java.time.LocalDate /** ViewModel to manage conjugate data download states and actions. */ +@Suppress("TooManyFunctions") class ConjugateDataDownloadViewModel( application: Application, ) : AndroidViewModel(application) { @@ -39,6 +40,9 @@ class ConjugateDataDownloadViewModel( private val _checkUpdateState = MutableStateFlow(CheckUpdateState.Idle) val checkUpdateState = _checkUpdateState.asStateFlow() + private val _toastMessage = MutableStateFlow(null) + val toastMessage = _toastMessage.asStateFlow() + private var checkUpdateJob: Job? = null /** @@ -98,6 +102,11 @@ class ConjugateDataDownloadViewModel( key: String, forceDownload: Boolean = false, ) { + if (!NetworkMonitor.isOnline(getApplication())) { + showToast(getApplication().getString(R.string.i18n_app_download_error_no_network)) + return + } + val currentState = downloadStates[key] ?: DownloadState.Ready val displayLang = key.replaceFirstChar { it.uppercase() } if (forceDownload) { @@ -113,7 +122,7 @@ class ConjugateDataDownloadViewModel( R.string.i18n_app_download_menu_ui_conjugate_data_already_up_to_date, ) val msg = StringUtils.formatStringWithParams(template, displayLang) - Toast.makeText(getApplication(), msg, Toast.LENGTH_SHORT).show() + showToast(msg) return } } @@ -156,7 +165,7 @@ class ConjugateDataDownloadViewModel( R.string.i18n_app_download_menu_ui_conjugate_data_download_success, ) val msg = StringUtils.formatStringWithParams(template, displayLang) - Toast.makeText(getApplication(), msg, Toast.LENGTH_SHORT).show() + showToast(msg) } } else { // Already up to date: Skip the DB work. @@ -166,7 +175,7 @@ class ConjugateDataDownloadViewModel( getApplication().getString( R.string.i18n_app_download_menu_ui_download_data_generic_already_up_to_date, ) - Toast.makeText(getApplication(), msg, Toast.LENGTH_SHORT).show() + showToast(msg) } } } catch (e: IOException) { @@ -209,6 +218,11 @@ class ConjugateDataDownloadViewModel( * Handles the "All languages" download action by initiating downloads for all languages that are not already completed or downloading. */ fun handleDownloadAllLanguages() { + if (!NetworkMonitor.isOnline(getApplication())) { + showToast(getApplication().getString(R.string.i18n_app_download_error_no_network)) + return + } + val toDownload = downloadStates.keys.filter { key -> downloadStates[key] != DownloadState.Completed && downloadStates[key] != DownloadState.Downloading @@ -272,6 +286,11 @@ class ConjugateDataDownloadViewModel( * Checks for new data updates for all completed languages. */ fun checkForNewData() { + if (!NetworkMonitor.isOnline(getApplication())) { + showToast(getApplication().getString(R.string.i18n_app_download_error_no_network)) + return + } + checkUpdateJob?.cancel() val keysToCheck = downloadStates.keys.filter { downloadStates[it] == DownloadState.Completed } @@ -314,7 +333,7 @@ class ConjugateDataDownloadViewModel( withContext(Dispatchers.Main) { // Reset status so user can retry. downloadStates[key] = DownloadState.Ready - Toast.makeText(getApplication(), message, Toast.LENGTH_LONG).show() + showToast(message) } } @@ -326,4 +345,12 @@ class ConjugateDataDownloadViewModel( downloadJobs.values.forEach { it.cancel() } downloadJobs.clear() } + + private fun showToast(msg: String) { + viewModelScope.launch { + _toastMessage.value = msg + kotlinx.coroutines.delay(3000) + _toastMessage.value = null + } + } } diff --git a/app/src/main/java/be/scri/ui/screens/download/DataDownloadViewModel.kt b/app/src/main/java/be/scri/ui/screens/download/DataDownloadViewModel.kt index 36a057e8..6978a481 100644 --- a/app/src/main/java/be/scri/ui/screens/download/DataDownloadViewModel.kt +++ b/app/src/main/java/be/scri/ui/screens/download/DataDownloadViewModel.kt @@ -6,7 +6,6 @@ import android.app.Application import android.content.Context import android.database.sqlite.SQLiteException import android.util.Log -import android.widget.Toast import androidx.compose.runtime.mutableStateMapOf import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope @@ -14,6 +13,7 @@ import be.scri.R import be.scri.data.remote.DynamicDbHelper import be.scri.data.remote.RetrofitClient import be.scri.helpers.LanguageMappingConstants +import be.scri.helpers.NetworkMonitor import be.scri.helpers.StringUtils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -29,6 +29,7 @@ import java.io.IOException import java.time.LocalDate /** ViewModel to manage data download states and actions. */ +@Suppress("TooManyFunctions") class DataDownloadViewModel( application: Application, ) : AndroidViewModel(application) { @@ -39,6 +40,9 @@ class DataDownloadViewModel( private val _checkUpdateState = MutableStateFlow(CheckUpdateState.Idle) val checkUpdateState = _checkUpdateState.asStateFlow() + private val _toastMessage = MutableStateFlow(null) + val toastMessage = _toastMessage.asStateFlow() + private var checkUpdateJob: Job? = null /** @@ -98,6 +102,11 @@ class DataDownloadViewModel( key: String, forceDownload: Boolean = false, ) { + if (!NetworkMonitor.isOnline(getApplication())) { + showToast(getApplication().getString(R.string.i18n_app_download_error_no_network)) + return + } + val currentState = downloadStates[key] ?: DownloadState.Ready val displayLang = key.replaceFirstChar { it.uppercase() } if (forceDownload) { @@ -113,7 +122,7 @@ class DataDownloadViewModel( R.string.i18n_app_download_menu_ui_download_data_already_up_to_date, ) val msg = StringUtils.formatStringWithParams(template, displayLang) - Toast.makeText(getApplication(), msg, Toast.LENGTH_SHORT).show() + showToast(msg) return } } @@ -156,7 +165,7 @@ class DataDownloadViewModel( R.string.i18n_app_download_menu_ui_download_data_download_success, ) val msg = StringUtils.formatStringWithParams(template, displayLang) - Toast.makeText(getApplication(), msg, Toast.LENGTH_SHORT).show() + showToast(msg) } } else { // Already up to date: Skip the DB work. @@ -166,7 +175,7 @@ class DataDownloadViewModel( getApplication().getString( R.string.i18n_app_download_menu_ui_download_data_generic_already_up_to_date, ) - Toast.makeText(getApplication(), msg, Toast.LENGTH_SHORT).show() + showToast(msg) } } } catch (e: IOException) { @@ -209,6 +218,11 @@ class DataDownloadViewModel( * Handles the "All languages" download action by initiating downloads for all languages that are not already completed or downloading. */ fun handleDownloadAllLanguages() { + if (!NetworkMonitor.isOnline(getApplication())) { + showToast(getApplication().getString(R.string.i18n_app_download_error_no_network)) + return + } + val toDownload = downloadStates.keys.filter { key -> downloadStates[key] != DownloadState.Completed && downloadStates[key] != DownloadState.Downloading @@ -272,6 +286,11 @@ class DataDownloadViewModel( * Checks for new data updates for all completed languages. */ fun checkForNewData() { + if (!NetworkMonitor.isOnline(getApplication())) { + showToast(getApplication().getString(R.string.i18n_app_download_error_no_network)) + return + } + checkUpdateJob?.cancel() val keysToCheck = downloadStates.keys.filter { downloadStates[it] == DownloadState.Completed } @@ -314,7 +333,7 @@ class DataDownloadViewModel( withContext(Dispatchers.Main) { // Reset status so user can retry. downloadStates[key] = DownloadState.Ready - Toast.makeText(getApplication(), message, Toast.LENGTH_LONG).show() + showToast(message) } } @@ -326,6 +345,14 @@ class DataDownloadViewModel( downloadJobs.values.forEach { it.cancel() } downloadJobs.clear() } + + private fun showToast(msg: String) { + viewModelScope.launch { + _toastMessage.value = msg + kotlinx.coroutines.delay(3000) + _toastMessage.value = null + } + } } /** diff --git a/app/src/main/res/values/string.xml b/app/src/main/res/values/string.xml index b9ab4db4..b1f24461 100644 --- a/app/src/main/res/values/string.xml +++ b/app/src/main/res/values/string.xml @@ -80,6 +80,7 @@ Network error: {error} Server error: {code} Download timed out + No internet connection. Please connect and try again. Add new data to Scribe Conjugate. Download verb data Download data to start conjugating! @@ -149,8 +150,6 @@ Have the device play a sound when keys are pressed. Vibrate on key press Have the device vibrate when keys are pressed. - Clipboard key on keyboard - Include a key to access the clipboard on the main keyboard. Default currency symbol Symbol for the 123 keys Select which currency symbol appears on the number keys. @@ -159,8 +158,6 @@ Select a keyboard layout that suits your typing preference and language needs. Disable accent characters Remove accented letter keys on the primary keyboard layout. - Floating key on keyboard - Include a key to float the keyboard UI on the main keyboard. Period and comma on ABC Include period and comma keys on the main keyboard for convenient typing. Layout