diff --git a/app/src/main/java/be/scri/App.kt b/app/src/main/java/be/scri/App.kt index a578966d..e7c2430d 100644 --- a/app/src/main/java/be/scri/App.kt +++ b/app/src/main/java/be/scri/App.kt @@ -21,6 +21,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable @@ -30,7 +31,6 @@ import be.scri.navigation.Screen import be.scri.ui.common.appcomponents.HintDialog import be.scri.ui.common.bottombar.ScribeBottomBar import be.scri.ui.screens.DefaultCurrencySymbolScreen -import be.scri.ui.screens.DownloadDataScreen import be.scri.ui.screens.InstallationScreen import be.scri.ui.screens.LanguageSettingsScreen import be.scri.ui.screens.PrivacyPolicyScreen @@ -38,6 +38,8 @@ import be.scri.ui.screens.SelectTranslationSourceLanguageScreen import be.scri.ui.screens.ThirdPartyScreen import be.scri.ui.screens.WikimediaScreen import be.scri.ui.screens.about.AboutScreen +import be.scri.ui.screens.download.DataDownloadViewModel +import be.scri.ui.screens.download.DownloadDataScreen import be.scri.ui.screens.settings.SettingsScreen import be.scri.ui.theme.ScribeTheme import kotlinx.coroutines.CoroutineScope @@ -60,7 +62,8 @@ import kotlinx.coroutines.launch * @param isDarkTheme Flag to indicate if dark theme is enabled. * @param modifier Optional layout modifier for UI customization. */ -@SuppressLint("ComposeModifierMissing") +@Suppress("LongParameterList") +@SuppressLint("ComposeModifierMissing", "LongParameterList") @Composable fun ScribeApp( pagerState: PagerState, @@ -72,9 +75,12 @@ fun ScribeApp( context: Context, isDarkTheme: Boolean, modifier: Modifier = Modifier, + downloadViewModel: DataDownloadViewModel = viewModel(), ) { val coroutineScope = rememberCoroutineScope() val navBackStackEntry by navController.currentBackStackEntryAsState() + val downloadStates = downloadViewModel.downloadStates + val onDownloadAction = downloadViewModel::handleDownloadAction ScribeTheme( useDarkTheme = isDarkTheme, @@ -199,6 +205,13 @@ fun ScribeApp( onBackNavigation = { navController.popBackStack() }, + onNavigateToTranslation = { language -> + navController.navigate( + "translation_language_detail/$language", + ) + }, + downloadStates = downloadStates, + onDownloadAction = onDownloadAction, modifier = Modifier.padding(innerPadding), ) } @@ -229,6 +242,10 @@ fun ScribeApp( onBackNavigation = { navController.popBackStack() }, + onNavigateToDownloadData = { + navController.popBackStack() + }, + onDownloadAction = onDownloadAction, modifier = Modifier.padding(innerPadding), currentLanguage = language, ) diff --git a/app/src/main/java/be/scri/extensions/CommonsContext.kt b/app/src/main/java/be/scri/extensions/CommonsContext.kt index f633ab21..8b257452 100644 --- a/app/src/main/java/be/scri/extensions/CommonsContext.kt +++ b/app/src/main/java/be/scri/extensions/CommonsContext.kt @@ -9,15 +9,15 @@ import be.scri.helpers.PREFS_KEY /** * Retrieves the shared preferences using the predefined PREFS_KEY. * - * @receiver Context used to access shared preferences - * @return SharedPreferences instance + * @receiver Context used to access shared preferences. + * @return SharedPreferences instance. */ fun Context.getSharedPrefs() = getSharedPreferences(PREFS_KEY, Context.MODE_PRIVATE) /** * Provides an instance of BaseConfig associated with the context. * - * @receiver Context used to create BaseConfig instance - * @return BaseConfig instance + * @receiver Context used to create BaseConfig instance. + * @return BaseConfig instance. */ val Context.baseConfig: BaseConfig get() = BaseConfig.newInstance(this) diff --git a/app/src/main/java/be/scri/extensions/ContextStyling.kt b/app/src/main/java/be/scri/extensions/ContextStyling.kt index 4b908a6b..43ae324d 100644 --- a/app/src/main/java/be/scri/extensions/ContextStyling.kt +++ b/app/src/main/java/be/scri/extensions/ContextStyling.kt @@ -13,8 +13,8 @@ import be.scri.helpers.DARK_GREY /** * Retrieves the appropriate text color based on the user's theme settings. * - * @receiver Context used to access resources and configuration - * @return Int representing the text color + * @receiver Context used to access resources and configuration. + * @return Int representing the text color. */ fun Context.getProperTextColor() = if (baseConfig.isUsingSystemTheme) { @@ -26,8 +26,8 @@ fun Context.getProperTextColor() = /** * Retrieves the appropriate key color based on the user's theme settings. * - * @receiver Context used to access resources and configuration - * @return Int representing the key color + * @receiver Context used to access resources and configuration. + * @return Int representing the key color. */ fun Context.getProperKeyColor() = @@ -40,8 +40,8 @@ fun Context.getProperKeyColor() = /** * Retrieves the appropriate background color based on the user's theme settings. * - * @receiver Context used to access resources and configuration - * @return Int representing the background color + * @receiver Context used to access resources and configuration. + * @return Int representing the background color. */ fun Context.getProperBackgroundColor() = if (baseConfig.isUsingSystemTheme) { @@ -53,8 +53,8 @@ fun Context.getProperBackgroundColor() = /** * Retrieves the appropriate primary color based on the user's theme settings. * - * @receiver Context used to access resources and configuration - * @return Int representing the primary color + * @receiver Context used to access resources and configuration. + * @return Int representing the primary color. */ fun Context.getProperPrimaryColor() = when { @@ -66,8 +66,8 @@ fun Context.getProperPrimaryColor() = /** * Determines if the current theme is black and white. * - * @receiver Context used to access configuration - * @return Boolean indicating if the theme is black and white + * @receiver Context used to access configuration. + * @return Boolean indicating if the theme is black and white. */ fun Context.isBlackAndWhiteTheme() = baseConfig.textColor == Color.WHITE && @@ -77,8 +77,8 @@ fun Context.isBlackAndWhiteTheme() = /** * Determines if the current theme is white. * - * @receiver Context used to access configuration - * @return Boolean indicating if the theme is white + * @receiver Context used to access configuration. + * @return Boolean indicating if the theme is white. */ fun Context.isWhiteTheme() = @@ -89,7 +89,7 @@ fun Context.isWhiteTheme() = /** * Determines if the system is using a dark theme. * - * @receiver Context used to access configuration - * @return Boolean indicating if the system is using a dark theme + * @receiver Context used to access configuration. + * @return Boolean indicating if the system is using a dark theme. */ fun Context.isUsingSystemDarkTheme() = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_YES != 0 diff --git a/app/src/main/java/be/scri/extensions/Drawable.kt b/app/src/main/java/be/scri/extensions/Drawable.kt index e1b1478f..23db6014 100644 --- a/app/src/main/java/be/scri/extensions/Drawable.kt +++ b/app/src/main/java/be/scri/extensions/Drawable.kt @@ -9,6 +9,7 @@ import android.graphics.drawable.Drawable * Applies a color filter to the drawable using the specified color. * * @param color The color to apply using the SRC_IN mode. + * * @return The mutated [Drawable] with the color filter applied. */ fun Drawable.applyColorFilter(color: Int) = mutate().setColorFilter(color, PorterDuff.Mode.SRC_IN) diff --git a/app/src/main/java/be/scri/extensions/Int.kt b/app/src/main/java/be/scri/extensions/Int.kt index 7ecc77c4..787e1ae9 100644 --- a/app/src/main/java/be/scri/extensions/Int.kt +++ b/app/src/main/java/be/scri/extensions/Int.kt @@ -34,6 +34,7 @@ fun Int.getContrastColor(): Int { * * @receiver Int The color to adjust. * @param factor Float The factor to multiply the alpha by (0f = fully transparent, 1f = original alpha). + * * @return Int The color with modified alpha. */ fun Int.adjustAlpha(factor: Float): Int { @@ -62,6 +63,7 @@ private const val FACTOR_DIVIDER = 100 * * @receiver Int The original color. * @param factor Int The factor to darken the color by (default is 8). + * * @return Int The darkened color. */ @@ -87,6 +89,7 @@ fun Int.darkenColor(factor: Int = DEFAULT_DARKEN_FACTOR): Int { * * @receiver Int The original color. * @param factor Int The factor to lighten the color by (default is 8). + * * @return Int The lightened color. */ @@ -113,6 +116,7 @@ private const val LIGHTNESS_THRESHOLD = 0.5f * Converts a color from HSL to HSV. * * @param hsl FloatArray The color in HSL format. + * * @return FloatArray The converted color in HSV format. */ private fun hsl2hsv(hsl: FloatArray): FloatArray { @@ -127,6 +131,7 @@ private fun hsl2hsv(hsl: FloatArray): FloatArray { * Converts a color from HSV to HSL. * * @param hsv FloatArray The color in HSV format. + * * @return FloatArray The converted color in HSL format. */ diff --git a/app/src/main/java/be/scri/helpers/AlphanumericComparator.kt b/app/src/main/java/be/scri/helpers/AlphanumericComparator.kt index 4cdaf020..bd8c1228 100644 --- a/app/src/main/java/be/scri/helpers/AlphanumericComparator.kt +++ b/app/src/main/java/be/scri/helpers/AlphanumericComparator.kt @@ -16,6 +16,7 @@ class AlphanumericComparator { * * @param string1 The first string to compare. * @param string2 The second string to compare. + * * @return A negative integer if `string1` comes before `string2`, * a positive integer if `string1` comes after `string2`, * and zero if they are equal. @@ -67,6 +68,7 @@ class AlphanumericComparator { * @param string The input string. * @param length The total length of the string. * @param marker The current position in the string from where extraction starts. + * * @return A substring representing a numeric or alphabetic chunk. */ private fun getChunk( @@ -105,7 +107,8 @@ class AlphanumericComparator { * Checks if the given character is a numeric digit. * * @param ch The character to check. - * @return `true` if the character is a digit (0-9), `false` otherwise. + * + * @return true if the character is a digit (0-9), false otherwise. */ private fun isDigit(ch: Char) = ch in '0'..'9' } diff --git a/app/src/main/java/be/scri/helpers/AnnotationTextUtils.kt b/app/src/main/java/be/scri/helpers/AnnotationTextUtils.kt index e158286d..17d8e918 100644 --- a/app/src/main/java/be/scri/helpers/AnnotationTextUtils.kt +++ b/app/src/main/java/be/scri/helpers/AnnotationTextUtils.kt @@ -17,7 +17,9 @@ import be.scri.helpers.LanguageMappingConstants.prepAnnotationConversionDict object AnnotationTextUtils { /** * Maps a case annotation string (e.g., "genitive case") to a displayable text and color. + * * @param nounType The case annotation string. + * * @return A pair containing the color resource ID and the display text. */ fun handleTextForCaseAnnotation( @@ -40,7 +42,9 @@ object AnnotationTextUtils { /** * Maps a noun type string (e.g., "masculine") to a displayable text and color. + * * @param nounType The noun type or gender string. + * * @return A pair containing the color resource ID and the display text. */ fun handleColorAndTextForNounType( @@ -62,8 +66,10 @@ object AnnotationTextUtils { /** * Processes a noun gender abbreviation for display, converting it based on language-specific conventions. + * * @param language The current keyboard language. * @param text The gender abbreviation (e.g., "M", "F", "N"). + * * @return The language-specific display text (e.g., "М" for Russian masculine). */ fun processValueForNouns( @@ -73,8 +79,10 @@ object AnnotationTextUtils { /** * Processes a preposition case abbreviation for display, converting it based on language-specific conventions. + * * @param language The current keyboard language. * @param text The case abbreviation (e.g., "Acc", "Dat"). + * * @return The language-specific display text (e.g., "Akk" for German accusative). */ fun processValuesForPreposition( diff --git a/app/src/main/java/be/scri/helpers/BaseConfig.kt b/app/src/main/java/be/scri/helpers/BaseConfig.kt index 9281cece..cae9e465 100644 --- a/app/src/main/java/be/scri/helpers/BaseConfig.kt +++ b/app/src/main/java/be/scri/helpers/BaseConfig.kt @@ -8,10 +8,8 @@ import be.scri.extensions.getSharedPrefs /** * A configuration helper class for managing app settings. - *

* This class provides access to various UI-related preferences such as colors, language settings, * and theme preferences. Preferences are stored using `SharedPreferences`. - *

* * @param context The application context used to retrieve resources and shared preferences. */ @@ -28,6 +26,7 @@ open class BaseConfig( * Creates a new instance of `BaseConfig`. * * @param context The application context. + * * @return A new instance of `BaseConfig`. */ fun newInstance(context: Context) = BaseConfig(context) diff --git a/app/src/main/java/be/scri/helpers/DatabaseFileManager.kt b/app/src/main/java/be/scri/helpers/DatabaseFileManager.kt index bff3743d..52b12167 100644 --- a/app/src/main/java/be/scri/helpers/DatabaseFileManager.kt +++ b/app/src/main/java/be/scri/helpers/DatabaseFileManager.kt @@ -12,6 +12,7 @@ import java.io.IOException /** * Manages access to all SQLite database files. * Ensures DB files are copied from assets and provides read-only connections. + * * @param context The application context. */ class DatabaseFileManager( @@ -31,6 +32,7 @@ class DatabaseFileManager( * It handles copying the database from assets if it doesn't exist locally. * * @param language The language code (e.g., "DE", "FR") used to determine the database filename. + * * @return An open, read-only [SQLiteDatabase] instance, or `null` on failure. */ fun getLanguageDatabase(language: String): SQLiteDatabase? { @@ -56,6 +58,7 @@ class DatabaseFileManager( * @param dbName The filename of the database (e.g., "ENLanguageData.sqlite"). * @param assetPath The path to the database file within the app's assets folder * (e.g., "data/ENLanguageData.sqlite"). + * * @return An open, read-only [SQLiteDatabase], or `null` if copying or opening fails. */ private fun getDatabase( @@ -85,7 +88,8 @@ class DatabaseFileManager( * * @param dbFile The destination [File] in the app's database directory. * @param assetPath The path to the source file within the assets folder. - * @return `true` if the copy was successful, `false` otherwise. + * + * @return true if the copy was successful, false otherwise. */ private fun copyDatabaseFromAssets( dbFile: File, diff --git a/app/src/main/java/be/scri/helpers/DatabaseManagers.kt b/app/src/main/java/be/scri/helpers/DatabaseManagers.kt index f2cc61fa..d9325f0e 100644 --- a/app/src/main/java/be/scri/helpers/DatabaseManagers.kt +++ b/app/src/main/java/be/scri/helpers/DatabaseManagers.kt @@ -41,6 +41,7 @@ class DatabaseManagers( * It delegates the loading and parsing logic to the [ContractDataLoader]. * * @param language The language code (e.g., "DE", "FR") for which to load the contract. + * * @return A [DataContract] object containing the language's structural metadata, or `null` * if not found or on error. */ diff --git a/app/src/main/java/be/scri/helpers/EmojiUtils.kt b/app/src/main/java/be/scri/helpers/EmojiUtils.kt index 7ce4188c..8b333b01 100644 --- a/app/src/main/java/be/scri/helpers/EmojiUtils.kt +++ b/app/src/main/java/be/scri/helpers/EmojiUtils.kt @@ -13,8 +13,10 @@ object EmojiUtils { /** * Checks if the end of a string is likely an emoji. * This is a heuristic check based on common emoji Unicode ranges. + * * @param word The string to check. - * @return `true` if the end of the string contains an emoji character, `false` otherwise. + * + * @return true if the end of the string contains an emoji character, false otherwise. */ fun isEmoji(word: String?): Boolean { if (word.isNullOrEmpty() || word.length < DATA_SIZE_2) { @@ -38,6 +40,7 @@ object EmojiUtils { /** * Inserts an emoji into the text field, replacing the keyword that triggered it if found. + * * @param emoji The emoji character to insert. */ fun insertEmoji( diff --git a/app/src/main/java/be/scri/helpers/KeyHandler.kt b/app/src/main/java/be/scri/helpers/KeyHandler.kt index 4ffa26e0..0f0e148a 100644 --- a/app/src/main/java/be/scri/helpers/KeyHandler.kt +++ b/app/src/main/java/be/scri/helpers/KeyHandler.kt @@ -69,7 +69,8 @@ class KeyHandler( * @param language The current keyboard language. * @param inputConnection The current input connection. * @param previousWasLastKeySpace The previous state of wasLastKeySpace. - * @return True to reset wasLastKeySpace, false to preserve it. + * + * @return true to reset wasLastKeySpace, false to preserve it. */ private fun processKeyCode( code: Int, @@ -122,6 +123,7 @@ class KeyHandler( * Handles the shift key press and returns whether to reset wasLastKeySpace at the end. * * @param previousWasLastKeySpace The previous state of wasLastKeySpace. + * * @return False to preserve wasLastKeySpace state, true to reset it. */ private fun handleShiftKeyPress(previousWasLastKeySpace: Boolean): Boolean { @@ -134,6 +136,7 @@ class KeyHandler( * Handles the space key press and returns whether to reset wasLastKeySpace at the end. * * @param previousWasLastKeySpace The previous state of wasLastKeySpace. + * * @return False to preserve wasLastKeySpace state, true to reset it. */ private fun handleSpaceKeyPress(previousWasLastKeySpace: Boolean): Boolean { @@ -146,7 +149,8 @@ class KeyHandler( * A valid state requires a non-null keyboard instance and an active input connection. * * @param inputConnection The current input connection. - * @return `true` if the state is valid, `false` otherwise. + * + * @return true if the state is valid, false otherwise. */ private fun isValidState(inputConnection: InputConnection?): Boolean = ime.keyboard != null && @@ -252,6 +256,7 @@ class KeyHandler( /** * Handles navigation keys (left/right arrows). + * * @param code The key code, used to determine direction. */ private fun handleNavigationKey(code: Int) { @@ -272,6 +277,7 @@ class KeyHandler( /** * Handles all special keys related to the Scribe command views (conjugation, etc.). + * * @param code The key code of the pressed key. * @param language The current keyboard language. */ diff --git a/app/src/main/java/be/scri/helpers/KeyboardBase.kt b/app/src/main/java/be/scri/helpers/KeyboardBase.kt index b53a7e0b..bfc98dcf 100644 --- a/app/src/main/java/be/scri/helpers/KeyboardBase.kt +++ b/app/src/main/java/be/scri/helpers/KeyboardBase.kt @@ -134,11 +134,12 @@ class KeyboardBase { /** * Retrieves the dimension or fraction value from the attributes, adjusting the base value if necessary. * - * @param a the TypedArray containing the attributes - * @param index the index of the desired attribute - * @param base the base value for the fraction calculation - * @param defValue the default value to return if no valid dimension is found - * @return the calculated dimension or fraction value + * @param a The TypedArray containing the attributes. + * @param index The index of the desired attribute. + * @param base The base value for the fraction calculation. + * @param defValue The default value to return if no valid dimension is found. + * + * @return The calculated dimension or fraction value. */ fun getDimensionOrFraction( a: TypedArray, @@ -301,11 +302,11 @@ class KeyboardBase { var repeatable = false /** Create a key with the given top-left coordinate and extract its attributes from the XML parser. - * @param res resources associated with the caller's context - * @param parent the row that this key belongs to. The row must already be attached to a [KeyboardBase]. - * @param x the x coordinate of the top-left - * @param y the y coordinate of the top-left - * @param parser the XML parser containing the attributes for this key + * @param res Resources associated with the caller's context. + * @param parent The row that this key belongs to. The row must already be attached to a [KeyboardBase]. + * @param x The x coordinate of the top-left. + * @param y The y coordinate of the top-left. + * @param parser The XML parser containing the attributes for this key. */ constructor(res: Resources, parent: Row, x: Int, y: Int, parser: XmlResourceParser?) : this(parent) { this.x = x @@ -364,8 +365,9 @@ class KeyboardBase { /** * Detects if a point falls inside this key. - * @param x the x-coordinate of the point - * @param y the y-coordinate of the point + * @param x The x-coordinate of the point. + * @param y The y-coordinate of the point. + * * @return whether or not the point falls inside the key. * If the key is attached to an edge, it will assume that all points between the key and * the edge are considered to be inside the key. @@ -387,10 +389,11 @@ class KeyboardBase { /** * Creates a keyboard from the given xml key layout file. - * Weeds out rows that have a keyboard mode defined but don't match the specified mode. - * @param context the application or service context - * @param xmlLayoutResId the resource file that contains the keyboard layout and keys. - * @param enterKeyType determines what icon should we show on Enter key + * Removes rows that have a keyboard mode defined but don't match the specified mode. + * + * @param context The application or service context. + * @param xmlLayoutResId The resource file that contains the keyboard layout and keys. + * @param enterKeyType Determines what icon should we show on Enter key. */ @JvmOverloads constructor( @@ -412,10 +415,11 @@ class KeyboardBase { * populates it with the specified characters in left-to-right, top-to-bottom fashion, * using the specified number of columns. If the specified number of columns is -1, * then the keyboard will fit as many keys as possible in each row. - * @param context the application or service context - * @param layoutTemplateResId the layout template file, containing no keys. - * @param characters the list of characters to display on the keyboard. One key will be created for each character. - * @param keyWidth the width of the popup key, make sure it is the same as the key itself + * + * @param context The application or service context. + * @param layoutTemplateResId The layout template file, containing no keys. + * @param characters The list of characters to display on the keyboard. One key will be created for each character. + * @param keyWidth The width of the popup key, make sure it is the same as the key itself. */ constructor(context: Context, layoutTemplateResId: Int, characters: CharSequence, keyWidth: Int) : this(context, layoutTemplateResId, 0) { @@ -458,8 +462,9 @@ class KeyboardBase { /** * Sets the keyboard shift state. * - * @param shiftState the new shift state to apply - * @return true if the shift state was changed; false otherwise + * @param shiftState The new shift state to apply. + * + * @return true if the shift state was changed, false otherwise. */ fun setShifted(shiftState: Int): Boolean { if (mShiftState != shiftState) { @@ -476,9 +481,10 @@ class KeyboardBase { /** * Creates a Row object from the XML resource parser. * - * @param res the resources associated with the context - * @param parser the XML resource parser - * @return the created Row object + * @param res The resources associated with the context. + * @param parser The XML resource parser. + * + * @return the created Row object. */ private fun createRowFromXml( res: Resources, @@ -489,12 +495,13 @@ class KeyboardBase { /** * Creates a Key object from the XML resource parser and the specified coordinates. * - * @param res the resources associated with the context - * @param parent the parent Row that this key belongs to - * @param x the x-coordinate of the key - * @param y the y-coordinate of the key - * @param parser the XML resource parser - * @return the created Key object + * @param res The resources associated with the context. + * @param parent The parent Row that this key belongs to. + * @param x The x-coordinate of the key. + * @param y The y-coordinate of the key. + * @param parser the XML resource parser. + * + * @return the created Key object. */ private fun createKeyFromXml( res: Resources, @@ -508,8 +515,8 @@ class KeyboardBase { * Loads the keyboard configuration from the provided XML parser, populating the rows and keys. * This method also handles edge cases like custom icons for the Enter key based on its type. * - * @param context the application context - * @param parser the XML resource parser + * @param context The application context. + * @param parser The XML resource parser. */ @SuppressLint("UseCompatLoadingForDrawables") private fun loadKeyboard( @@ -646,8 +653,8 @@ class KeyboardBase { /** * Parses the keyboard attributes such as key width, height, and horizontal gap from the XML resource. * - * @param res the resources associated with the context - * @param parser the XML resource parser + * @param res The resources associated with the context. + * @param parser The XML resource parser. */ private fun parseKeyboardAttributes( res: Resources, diff --git a/app/src/main/java/be/scri/helpers/LanguageMappingConstants.kt b/app/src/main/java/be/scri/helpers/LanguageMappingConstants.kt index b6dfb4f9..5826aed9 100644 --- a/app/src/main/java/be/scri/helpers/LanguageMappingConstants.kt +++ b/app/src/main/java/be/scri/helpers/LanguageMappingConstants.kt @@ -73,7 +73,9 @@ object LanguageMappingConstants { /** * Converts a full language name (e.g., "English") to its two-letter ISO alias (e.g., "EN"). + * * @param language The full name of the language. + * * @return The two-letter alias. */ fun getLanguageAlias(language: String): String = diff --git a/app/src/main/java/be/scri/helpers/PreferencesHelper.kt b/app/src/main/java/be/scri/helpers/PreferencesHelper.kt index c23076bd..f5da00b1 100644 --- a/app/src/main/java/be/scri/helpers/PreferencesHelper.kt +++ b/app/src/main/java/be/scri/helpers/PreferencesHelper.kt @@ -50,6 +50,7 @@ object PreferencesHelper { * * @param context The application context. * @param language The language for which to get the translation source. + * * @return The translation source language. */ fun getTranslationSourceLanguage( @@ -68,6 +69,7 @@ object PreferencesHelper { * * @param key The base key. * @param language The language for which to generate the preference key. + * * @return The generated language-specific preference key. */ fun getLanguageSpecificPreferenceKey( @@ -297,6 +299,7 @@ object PreferencesHelper { * Retrieves the user's dark mode preference. * * @param context The application context. + * * @return The dark mode setting as an integer value (AppCompatDelegate.MODE_NIGHT_YES or MODE_NIGHT_NO). */ fun getUserDarkModePreference(context: Context): Int { @@ -319,7 +322,8 @@ object PreferencesHelper { * * @param context The application context. * @param language The language for which to check the preference. - * @return True if accent characters are disabled, false otherwise. + * + * @return true if accent characters are disabled, false otherwise. */ fun getIsAccentCharacterDisabled( context: Context, @@ -336,7 +340,8 @@ object PreferencesHelper { * * @param context The application context. * @param language The language for which to check the preference. - * @return True if the preview feature is enabled, false otherwise. + * + * @return true if the preview feature is enabled, false otherwise. */ fun isShowPopupOnKeypressEnabled( context: Context, @@ -349,11 +354,12 @@ object PreferencesHelper { } /** - * Retrieves whether vibration on keypress is enabled for a given language. + * Retrieves whether vibration on key press is enabled for a given language. * * @param context The application context. * @param language The language for which to check the preference. - * @return True if vibration on keypress is enabled, false otherwise. + * + * @return true if vibration on key press is enabled, false otherwise. */ fun getIsVibrateEnabled( context: Context, @@ -380,7 +386,8 @@ object PreferencesHelper { * * @param context The application context. * @param language The language for which to check the preference. - * @return True if period and comma are enabled, false otherwise. + * + * @return true if period and comma are enabled, false otherwise. */ fun getEnablePeriodAndCommaABC( context: Context, @@ -397,7 +404,8 @@ object PreferencesHelper { * * @param context The application context. * @param language The language for which to check the preference. - * @return True if double tap on space enabled for a given language, false otherwise. + * + * @return true if double tap on space enabled for a given language, false otherwise. */ fun getEnablePeriodOnSpaceBarDoubleTap( context: Context, @@ -411,7 +419,8 @@ object PreferencesHelper { * Retrieves whether dark mode is enabled based on user preferences or system settings. * * @param context The application context. - * @return True if dark mode is enabled, false otherwise. + * + * @return true if dark mode is enabled, false otherwise. */ fun getIsDarkModeOrNot(context: Context): Boolean { val sharedPref = context.getSharedPreferences(SCRIBE_PREFS, MODE_PRIVATE) @@ -426,7 +435,8 @@ object PreferencesHelper { * * @param context The application context. * @param language The language for which to check the preference. - * @return True if emoji suggestions are enabled, false otherwise. + * + * @return true if emoji suggestions are enabled, false otherwise. */ fun getIsEmojiSuggestionsEnabled( context: Context, @@ -442,7 +452,8 @@ object PreferencesHelper { * * @param context The application context. * @param language The language for which to check the preference. - * @return True if word-by-word deletion is enabled, false otherwise. + * + * @return true if word-by-word deletion is enabled, false otherwise. */ fun getIsWordByWordDeletionEnabled( context: Context, @@ -495,6 +506,7 @@ object PreferencesHelper { * * @param context The application context. * @param language The language for which to get the currency preference. + * * @return The currency symbol (e.g., "$", "€"). */ fun getDefaultCurrencySymbol( @@ -529,6 +541,7 @@ object PreferencesHelper { * * @param context The application context. * @param language The language for which to get the currency preference. + * * @return The currency name (e.g., "Dollar", "Euro"). */ fun getDefaultCurrencyName( @@ -569,6 +582,7 @@ object PreferencesHelper { * * @param context The application context. * @param language The language for which to get the currency preference. + * * @return The state of hold key style. */ fun getHoldKeyStyle( diff --git a/app/src/main/java/be/scri/helpers/SpaceKeyProcessor.kt b/app/src/main/java/be/scri/helpers/SpaceKeyProcessor.kt index 439d5355..eaaef455 100644 --- a/app/src/main/java/be/scri/helpers/SpaceKeyProcessor.kt +++ b/app/src/main/java/be/scri/helpers/SpaceKeyProcessor.kt @@ -23,6 +23,7 @@ class SpaceKeyProcessor( * If in command bar mode, it treats space as a regular character input. * * @param currentWasLastKeySpace The state of whether the previous key was a space. + * * @return The new state for `wasLastKeySpace` after processing the space key. */ fun processKeycodeSpace(currentWasLastKeySpace: Boolean): Boolean { @@ -56,7 +57,8 @@ class SpaceKeyProcessor( * Handles space key press when not in command bar mode. * This includes the "period on double tap" logic if enabled and applicable, * otherwise commits a normal space. Updates word suggestions. - * @param wasLastKeySpace True if the previous key pressed was a space. + * + * @param wasLastKeySpace true if the previous key pressed was a space. */ private fun handleSpaceOutsideCommandBar(wasLastKeySpace: Boolean) { val periodOnDoubleTapEnabled = PreferencesHelper.getEnablePeriodOnSpaceBarDoubleTap(context = ime, ime.language) @@ -129,8 +131,10 @@ class SpaceKeyProcessor( * on a double space when the text before is two characters long. * Criteria: not null, length is 2, starts with a space, and does not end with " .". * This typically matches patterns like " X" (where X is not '.') or " ". + * * @param textBefore The two characters of text immediately before the cursor. - * @return True if the conditions are met, false otherwise. + * + * @return true if the conditions are met, false otherwise. */ private fun meetsTwoCharDoubleSpacePeriodCondition(textBefore: String?): Boolean = textBefore != null && diff --git a/app/src/main/java/be/scri/helpers/StringUtils.kt b/app/src/main/java/be/scri/helpers/StringUtils.kt index 541bf7b8..4e7f8ca0 100644 --- a/app/src/main/java/be/scri/helpers/StringUtils.kt +++ b/app/src/main/java/be/scri/helpers/StringUtils.kt @@ -7,8 +7,10 @@ package be.scri.helpers object StringUtils { /** * Checks if a word is capitalized (i.e., starts with an uppercase letter). + * * @param word The word to check. - * @return `true` if the word is capitalized, `false` otherwise. + * + * @return true if the word is capitalized, false otherwise. */ fun isWordCapitalized(word: String): Boolean { if (word.isEmpty()) return false diff --git a/app/src/main/java/be/scri/helpers/data/AutocompletionDataManager.kt b/app/src/main/java/be/scri/helpers/data/AutocompletionDataManager.kt index c37c1bbf..7be20938 100644 --- a/app/src/main/java/be/scri/helpers/data/AutocompletionDataManager.kt +++ b/app/src/main/java/be/scri/helpers/data/AutocompletionDataManager.kt @@ -36,6 +36,7 @@ class AutocompletionDataManager( * * @param prefix The starting text to search for (e.g. "ap"). * @param limit The maximum number of suggestions to return (default: 3). + * * @return A list of matching words that begin with the prefix. */ fun getAutocompletions( diff --git a/app/src/main/java/be/scri/helpers/data/ConjugateDataManager.kt b/app/src/main/java/be/scri/helpers/data/ConjugateDataManager.kt index 8c2ab629..d0678fba 100644 --- a/app/src/main/java/be/scri/helpers/data/ConjugateDataManager.kt +++ b/app/src/main/java/be/scri/helpers/data/ConjugateDataManager.kt @@ -22,6 +22,7 @@ class ConjugateDataManager( * @param language The language code (e.g., "EN", "SV") to determine the correct database. * @param jsonData The data contract for the language, which defines the structure of conjugations. * @param word The specific verb to look up conjugations for. + * * @return A nested map where the outer key is the tense group title * (e.g., "Indicative"), the inner key is the * conjugation category title (e.g., "Present"), and the value is a collection of the conjugated forms. @@ -56,6 +57,7 @@ class ConjugateDataManager( * * @param jsonData The data contract containing the conjugation structure. * @param word The base word, which is also added to the set. + * * @return A `Set` of unique strings representing all possible conjugation form identifiers. */ fun extractConjugateHeadings( @@ -78,6 +80,7 @@ class ConjugateDataManager( * @param word The base word (verb) to look up. * @param form The specific conjugation form identifier (e.g., "1ps", "past_participle"). * @param language The language code to select the correct database. + * * @return The conjugated word as a [String], or an empty string if not found. */ private fun getTheValueForTheConjugateWord( @@ -99,6 +102,7 @@ class ConjugateDataManager( * * @param cursor The database cursor positioned at the correct row for the verb. * @param form The form identifier, which can be a simple column name or a complex string. + * * @return The conjugated value, or an empty string on failure. */ private fun getConjugatedValueFromCursor( @@ -124,6 +128,7 @@ class ConjugateDataManager( * * @param cursor The database cursor positioned at the correct row. * @param form The complex form string to parse. + * * @return The combined string (e.g., "have walked"), or an empty string on failure. */ private fun parseComplexForm( @@ -208,6 +213,7 @@ class ConjugateDataManager( * @param db The SQLite database instance to query. * @param word The verb to search for. * @param language The language code, used for special query conditions. + * * @return A [Cursor] positioned at the verb's row, or null if the verb is not found. * The caller is responsible for closing the cursor. */ diff --git a/app/src/main/java/be/scri/helpers/data/ContractDataLoader.kt b/app/src/main/java/be/scri/helpers/data/ContractDataLoader.kt index 48d1349c..085abc8b 100644 --- a/app/src/main/java/be/scri/helpers/data/ContractDataLoader.kt +++ b/app/src/main/java/be/scri/helpers/data/ContractDataLoader.kt @@ -20,6 +20,7 @@ class ContractDataLoader( * It gracefully handles file-not-found and JSON parsing errors by returning null. * * @param language The language code (e.g., "DE", "EN") used to determine the filename (e.g., "de.json"). + * * @return The decoded [DataContract] object if successful, or `null` * if the file does not exist or cannot be parsed. */ diff --git a/app/src/main/java/be/scri/helpers/data/EmojiDataManager.kt b/app/src/main/java/be/scri/helpers/data/EmojiDataManager.kt index a7b239f9..0c02bcfc 100644 --- a/app/src/main/java/be/scri/helpers/data/EmojiDataManager.kt +++ b/app/src/main/java/be/scri/helpers/data/EmojiDataManager.kt @@ -6,6 +6,7 @@ import be.scri.helpers.DatabaseFileManager /** * Manages emoji keywords by querying an SQLite database. + * * @param fileManager The central manager for database file access. */ class EmojiDataManager( @@ -20,6 +21,7 @@ class EmojiDataManager( * As a side effect, it also calculates and stores the maximum length of any keyword found. * * @param language The language code (e.g., "DE", "FR") to select the correct database. + * * @return A [HashMap] where keys are lowercase words and values are a list of associated emoji strings. */ fun getEmojiKeywords(language: String): HashMap> { diff --git a/app/src/main/java/be/scri/helpers/data/GenderDataManager.kt b/app/src/main/java/be/scri/helpers/data/GenderDataManager.kt index d36c5378..ebed9cd4 100644 --- a/app/src/main/java/be/scri/helpers/data/GenderDataManager.kt +++ b/app/src/main/java/be/scri/helpers/data/GenderDataManager.kt @@ -19,6 +19,7 @@ class GenderDataManager( * * @param language The language code (e.g., "DE", "FR") to select the correct database. * @param contract The data contract for the language, which defines the gender-related database columns. + * * @return A [HashMap] where keys are lowercase nouns and values are a list of their gender(s) (e.g., "masculine"). */ fun findGenderOfWord( @@ -37,6 +38,7 @@ class GenderDataManager( * * @param db The SQLite database instance. * @param contract The data contract defining how gender is stored for this language. + * * @return A [HashMap] of nouns to their genders. */ private fun processGenderData( @@ -74,8 +76,10 @@ class GenderDataManager( /** * Checks if the data contract defines a single, canonical gender column. + * * @param contract The data contract to check. - * @return `true` if a canonical gender column is specified, `false` otherwise. + * + * @return true if a canonical gender column is specified, false otherwise. */ private fun hasCanonicalGender(contract: DataContract): Boolean = contract.genders.canonical @@ -84,8 +88,10 @@ class GenderDataManager( /** * Checks if the data contract defines separate columns for masculine and feminine genders. + * * @param contract The data contract to check. - * @return `true` if both masculine and feminine columns are specified, `false` otherwise. + * + * @return true if both masculine and feminine columns are specified, false otherwise. */ private fun hasMasculineFeminine(contract: DataContract): Boolean { val masculineList = contract.genders.masculines diff --git a/app/src/main/java/be/scri/helpers/data/PluralFormsManager.kt b/app/src/main/java/be/scri/helpers/data/PluralFormsManager.kt index b5bff7f1..eb1c4d0a 100644 --- a/app/src/main/java/be/scri/helpers/data/PluralFormsManager.kt +++ b/app/src/main/java/be/scri/helpers/data/PluralFormsManager.kt @@ -18,6 +18,7 @@ class PluralFormsManager( * * @param language The language code (e.g., "EN", "DE") to select the correct database. * @param jsonData The data contract, which specifies the names of the columns containing plural forms. + * * @return A [List] of all plural word forms, or `null` * if the operation fails or no plural columns are defined. */ @@ -37,6 +38,7 @@ class PluralFormsManager( * @param language The language code to select the correct database. * @param jsonData The data contract, which specifies the singular and plural column names. * @param noun The singular noun to find the plural for. + * * @return A [Map] containing the singular noun as the key and * its plural form as the value, or an empty map if not found. */ @@ -79,6 +81,7 @@ class PluralFormsManager( * @param singularCol The name of the column containing singular nouns. * @param pluralCol The name of the column containing the corresponding plural nouns. * @param noun The specific singular noun to search for. + * * @return A map of the singular noun to its plural, or an empty map if not found. */ private fun querySpecificPlural( @@ -109,6 +112,7 @@ class PluralFormsManager( * * @param db The SQLite database to query. * @param pluralColumns A list of column names that contain plural forms. + * * @return A [List] of all plural words found in the specified columns. */ private fun queryAllPluralForms( diff --git a/app/src/main/java/be/scri/helpers/data/PrepositionDataManager.kt b/app/src/main/java/be/scri/helpers/data/PrepositionDataManager.kt index 60716476..1750e154 100644 --- a/app/src/main/java/be/scri/helpers/data/PrepositionDataManager.kt +++ b/app/src/main/java/be/scri/helpers/data/PrepositionDataManager.kt @@ -7,6 +7,7 @@ import be.scri.helpers.DatabaseFileManager /** * Manages preposition data and case annotations from the database. + * * @param fileManager The central manager for database file access. */ class PrepositionDataManager( @@ -17,6 +18,7 @@ class PrepositionDataManager( * This functionality is currently only supported for German ("DE") and Russian ("RU"). * * @param language The language code. + * * @return A [HashMap] where keys are prepositions and values are a list of required cases * (e.g., "accusative case"). * Returns an empty map for unsupported languages or on failure. @@ -36,6 +38,7 @@ class PrepositionDataManager( * Iterates through a database cursor from the `prepositions` table and populates a map with the results. * * @param cursor The cursor containing the preposition and grammatical case data. + * * @return A [HashMap] mapping prepositions to a list of their cases. */ private fun processCursor(cursor: Cursor): HashMap> { diff --git a/app/src/main/java/be/scri/helpers/data/TranslationDataManager.kt b/app/src/main/java/be/scri/helpers/data/TranslationDataManager.kt index 15897c82..58c1f73c 100644 --- a/app/src/main/java/be/scri/helpers/data/TranslationDataManager.kt +++ b/app/src/main/java/be/scri/helpers/data/TranslationDataManager.kt @@ -9,6 +9,7 @@ import be.scri.helpers.StringUtils.isWordCapitalized /** * Manages translations from a local SQLite database. + * * @param context The application context. * @param fileManager The central manager for database file access. */ @@ -21,6 +22,7 @@ class TranslationDataManager( * The source is derived from user preferences, and the destination is the current keyboard language. * * @param language The current keyboard language name (e.g., "English"). + * * @return A [Pair] containing the source and destination ISO codes (e.g., "en" to "fr"). */ fun getSourceAndDestinationLanguage(language: String): Pair { @@ -34,6 +36,7 @@ class TranslationDataManager( * * @param sourceAndDestination A [Pair] of source and destination ISO language codes. * @param word The word to be translated. + * * @return The translated word as a [String], or an empty string if no translation is found. */ fun getTranslationDataForAWord( @@ -96,6 +99,7 @@ class TranslationDataManager( * @param destColumn The name of the column containing the translation * (derived from the destination language ISO code, e.g., "fr"). * @param word The word to search for in the 'word' column of the source table. + * * @return The translated word, or an empty string if not found. */ private fun queryForTranslation( @@ -124,6 +128,7 @@ class TranslationDataManager( * Converts a full language name (e.g., "english") to its corresponding two-letter ISO 639-1 code. * * @param languageName The full name of the language. + * * @return The two-letter ISO code as a [String]. Defaults to "en". */ private fun generateISOCodeForLanguage(languageName: String): String = @@ -143,6 +148,7 @@ class TranslationDataManager( * Converts a two-letter ISO 639-1 code to its corresponding full language name used for table lookups. * * @param isoCode The two-letter ISO code. + * * @return The full language name as a [String]. Defaults to "english". */ private fun generateLanguageNameForISOCode(isoCode: String): String = diff --git a/app/src/main/java/be/scri/helpers/data/Trie.kt b/app/src/main/java/be/scri/helpers/data/Trie.kt index 6db24a81..611bb252 100644 --- a/app/src/main/java/be/scri/helpers/data/Trie.kt +++ b/app/src/main/java/be/scri/helpers/data/Trie.kt @@ -44,6 +44,7 @@ class Trie { * * @param prefix The prefix to search for. * @param limit The maximum number of results to return (default = 10). + * * @return A list of words that begin with the prefix, up to [limit]. * * Example: diff --git a/app/src/main/java/be/scri/helpers/ui/HintUtils.kt b/app/src/main/java/be/scri/helpers/ui/HintUtils.kt index b1e691c9..6a95c60e 100644 --- a/app/src/main/java/be/scri/helpers/ui/HintUtils.kt +++ b/app/src/main/java/be/scri/helpers/ui/HintUtils.kt @@ -43,6 +43,7 @@ object HintUtils { * * @param currentState The current state of the keyboard. * @param language The language code (e.g., "English", "French"). + * * @return The appropriate hint message for the given state and language. */ fun getCommandBarHint( @@ -59,6 +60,7 @@ object HintUtils { * Maps the current state of the keyboard to its corresponding hints. * * @param currentState The current state of the keyboard. + * * @return A map of language codes to hint strings for the current state. */ private fun getHintForState(currentState: GeneralKeyboardIME.ScribeState): Map = @@ -160,6 +162,7 @@ object HintUtils { * * @param currentState The current state of the keyboard. * @param language The language code (e.g., "English", "French"). + * * @return The appropriate prompt text for the given state and language. */ fun getPromptText( @@ -180,6 +183,7 @@ object HintUtils { * Retrieves the translation prompt text for the given language. * * @param language The language code (e.g., "English", "French"). + * * @return The translation prompt text for the given language. */ private fun getTranslationPrompt( @@ -211,6 +215,7 @@ object HintUtils { * Retrieves the conjugation prompt text for the given language. * * @param language The language code (e.g., "English", "French"). + * * @return The conjugation prompt text for the given language. */ private fun getConjugationPrompt(language: String): String = @@ -230,6 +235,7 @@ object HintUtils { * Retrieves the plural prompt text for the given language. * * @param language The language code (e.g., "English", "French"). + * * @return The plural prompt text for the given language. */ private fun getPluralPrompt(language: String): String = diff --git a/app/src/main/java/be/scri/helpers/ui/RatingHelper.kt b/app/src/main/java/be/scri/helpers/ui/RatingHelper.kt index 8234af72..87823929 100644 --- a/app/src/main/java/be/scri/helpers/ui/RatingHelper.kt +++ b/app/src/main/java/be/scri/helpers/ui/RatingHelper.kt @@ -23,10 +23,10 @@ object RatingHelper { /** * Gets the package name of the app that installed this app. - * * For example, "com.android.vending" for Google Play Store. * * @param context App context. + * * @return Installer package name, or null if unknown or on error. Logs errors. */ private fun getInstallSource(context: Context): String? = diff --git a/app/src/main/java/be/scri/helpers/ui/ShareHelper.kt b/app/src/main/java/be/scri/helpers/ui/ShareHelper.kt index 51bd3f68..9031a106 100644 --- a/app/src/main/java/be/scri/helpers/ui/ShareHelper.kt +++ b/app/src/main/java/be/scri/helpers/ui/ShareHelper.kt @@ -11,6 +11,7 @@ import android.util.Log interface ShareHelperInterface { /** * Shares scribe. + * * @param context The Android context used to perform the share action. */ fun shareScribe(context: Context) diff --git a/app/src/main/java/be/scri/services/GeneralKeyboardIME.kt b/app/src/main/java/be/scri/services/GeneralKeyboardIME.kt index 42b771c7..a50b90c9 100644 --- a/app/src/main/java/be/scri/services/GeneralKeyboardIME.kt +++ b/app/src/main/java/be/scri/services/GeneralKeyboardIME.kt @@ -180,7 +180,7 @@ abstract class GeneralKeyboardIME( * 2. The input type variation for URIs (common in address bars). * 3. The hint text for keywords like "search" or "address". * - * @return `true` if the current input field is likely a search or address bar, `false` otherwise. + * @return true if the current input field is likely a search or address bar, false otherwise. */ fun isSearchBar(): Boolean { val editorInfo = currentInputEditorInfo @@ -212,7 +212,8 @@ abstract class GeneralKeyboardIME( /** * Returns whether the current conjugation state requires a subsequent selection view. * This is used, for example, when a conjugation form has multiple options (e.g., "am/is/are" in English). - * @return `true` if a subsequent selection screen is needed, `false` otherwise. + * + * @return true if a subsequent selection screen is needed, false otherwise. */ internal fun returnIsSubsequentRequired(): Boolean = subsequentAreaRequired @@ -231,6 +232,7 @@ abstract class GeneralKeyboardIME( /** * Creates the main view for the input method, inflating it from XML and setting up the keyboard. + * * @return The root View of the input method. */ override fun onCreateInputView(): View { @@ -316,8 +318,9 @@ abstract class GeneralKeyboardIME( } /** - * Called when the input view is finished. Resets the keyboard state to IDLE. - * @param finishingInput `true` if we are finishing for good, + * Called when the input view is finished. Resets the keyboard state to idle. + * + * @param finishingInput true if we are finishing for good, * `false` if just switching to another app. */ override fun onFinishInputView(finishingInput: Boolean) { @@ -338,7 +341,8 @@ abstract class GeneralKeyboardIME( /** * Overrides the default implementation to check if there is any * non-whitespace text before the cursor. - * @return `true` if there is meaningful text before the cursor, `false` otherwise. + * + * @return true if there is meaningful text before the cursor, false otherwise. */ override fun hasTextBeforeCursor(): Boolean { val ic = currentInputConnection ?: return false @@ -348,6 +352,7 @@ abstract class GeneralKeyboardIME( /** * Called when a key is pressed down. Triggers haptic feedback if enabled. + * * @param primaryCode The integer code of the key that was pressed. */ override fun onPress(primaryCode: Int) { @@ -406,8 +411,9 @@ abstract class GeneralKeyboardIME( /** * Called when the IME is starting to interact with a new input field. * It initializes the keyboard based on the input type and loads all language-specific data. + * * @param attribute The editor information for the new input field. - * @param restarting `true` if we are restarting the input with the same editor. + * @param restarting true if we are restarting the input with the same editor. */ override fun onStartInput( attribute: EditorInfo?, @@ -457,8 +463,9 @@ abstract class GeneralKeyboardIME( /** * Called when the input view is starting. It sets up the UI theme, emoji settings, * and initial keyboard state. + * * @param editorInfo The editor information for the input field. - * @param restarting `true` if we are restarting the input with the same editor. + * @param restarting true if we are restarting the input with the same editor. */ override fun onStartInputView( editorInfo: EditorInfo?, @@ -526,6 +533,7 @@ abstract class GeneralKeyboardIME( /** * Updates the color of the Enter key based on the current Scribe state and theme (dark/light mode). + * * @param isDarkMode The current dark mode status. If null, it will be determined from context. */ private fun updateEnterKeyColor(isDarkMode: Boolean?) { @@ -547,6 +555,7 @@ abstract class GeneralKeyboardIME( /** * Updates the hint and prompt text displayed in the command bar area based on the current state. + * * @param isUserDarkMode The current dark mode status. If null, it will be determined from context. * @param text A specific text to be displayed in the prompt, often used for conjugation titles. * @param word A word to be included in the hint text. @@ -1070,9 +1079,11 @@ abstract class GeneralKeyboardIME( /** * Determines which keyboard layout XML to use based on the current [ScribeState]. + * * @param state The current state of the Scribe keyboard. - * @param isSubsequentArea `true` if this is for a secondary conjugation view. + * @param isSubsequentArea true if this is for a secondary conjugation view. * @param dataSize The number of items to display, used to select an appropriate layout. + * * @return The resource ID of the keyboard layout XML. */ private fun getKeyboardLayoutForState( @@ -1103,6 +1114,7 @@ abstract class GeneralKeyboardIME( /** * Initializes or re-initializes the keyboard with a new layout. + * * @param xmlId The resource ID of the keyboard layout XML. */ private fun initializeKeyboard(xmlId: Int) { @@ -1127,6 +1139,7 @@ abstract class GeneralKeyboardIME( /** * Retrieves and validates the stored index for the current conjugation view. * Ensures the index is within the bounds of available conjugation types. + * * @return A valid, zero-based index for the conjugation type. */ private fun getValidatedConjugateIndex(): Int { @@ -1140,8 +1153,9 @@ abstract class GeneralKeyboardIME( /** * A wrapper to set up the conjugation key labels for the current language and index. + * * @param conjugateIndex The index of the conjugation tense/mood to display. - * @param isSubsequentArea `true` if setting up a secondary view. + * @param isSubsequentArea true if setting up a secondary view. */ internal fun setupConjugateKeysByLanguage( conjugateIndex: Int, @@ -1155,8 +1169,9 @@ abstract class GeneralKeyboardIME( /** * Sets the labels for the special conjugation keys based on the selected tense/mood. + * * @param startIndex The index of the conjugation tense/mood from the loaded data. - * @param isSubsequentArea `true` if this is for a secondary conjugation view. + * @param isSubsequentArea true if this is for a secondary conjugation view. */ private fun setUpConjugateKeys( startIndex: Int, @@ -1186,6 +1201,7 @@ abstract class GeneralKeyboardIME( /** * Sets up conjugation key labels for non-English languages, which typically follow a 3x2 grid layout. + * * @param languageOutput The map of conjugation forms for the selected tense. * @param conjugateLabel The list of labels for each person/number (e.g., "1ps", "2ps"). * @param title The title of the current tense/mood. @@ -1227,8 +1243,9 @@ abstract class GeneralKeyboardIME( /** * Sets up conjugation key labels for English, which has a more complex structure, * potentially requiring a subsequent selection view. + * * @param languageOutput The map of conjugation forms for the selected tense. - * @param isSubsequentArea `true` if this is for a secondary view. + * @param isSubsequentArea true if this is for a secondary view. */ private fun setUpEnglishConjugateKeys( languageOutput: Map>, @@ -1269,6 +1286,7 @@ abstract class GeneralKeyboardIME( /** * Sets up a secondary "sub-view" for conjugation when a single key has multiple options. + * * @param data The full dataset of subsequent options. * @param word The specific word selected from the primary view, used to filter the data. */ @@ -1304,8 +1322,9 @@ abstract class GeneralKeyboardIME( /** * Saves the type of conjugation layout being used (e.g., "2x2", "3x2") to shared preferences. + * * @param language The current keyboard language. - * @param isSubsequentArea `true` if this is for a secondary view. + * @param isSubsequentArea true if this is for a secondary view. */ internal fun saveConjugateModeType( language: String, @@ -1328,7 +1347,8 @@ abstract class GeneralKeyboardIME( /** * Updates the visibility of the suggestion buttons based on device type (phone/tablet) * and whether auto-suggestions are currently active. - * @param isAutoSuggestEnabled `true` if emoji or linguistic suggestions are available. + * + * @param isAutoSuggestEnabled true if emoji or linguistic suggestions are available. */ internal fun updateButtonVisibility(isAutoSuggestEnabled: Boolean) { if (currentState != ScribeState.IDLE) { @@ -1367,6 +1387,7 @@ abstract class GeneralKeyboardIME( /** * Handles the logic for showing/hiding suggestion buttons specifically on tablet layouts. + * * @param emojiCount The number of available emoji suggestions. */ private fun updateTabletButtonVisibility(emojiCount: Int) { @@ -1417,6 +1438,7 @@ abstract class GeneralKeyboardIME( /** * Handles the logic for showing/hiding suggestion buttons specifically on phone layouts. + * * @param emojiCount The number of available emoji suggestions. */ private fun updatePhoneButtonVisibility(emojiCount: Int) { @@ -1458,20 +1480,24 @@ abstract class GeneralKeyboardIME( /** * Retrieves the text immediately preceding the cursor. + * * @return The text before the cursor, up to a defined maximum length. */ fun getText(): String? = currentInputConnection?.getTextBeforeCursor(TEXT_LENGTH, 0)?.toString() /** * Extracts the last word from the text immediately preceding the cursor. + * * @return The last word as a [String], or null if no word is found. */ fun getLastWordBeforeCursor(): String? = getText()?.trim()?.split("\\s+".toRegex())?.lastOrNull() /** * Finds associated emojis for the last typed word. + * * @param emojiKeywords The map of keywords to emojis. * @param lastWord The word to look up. + * * @return A mutable list of emoji suggestions, or null if none are found. */ fun findEmojisForLastWord( @@ -1484,8 +1510,10 @@ abstract class GeneralKeyboardIME( /** * Finds the grammatical gender(s) for the last typed word. + * * @param nounKeywords The map of nouns to their genders. * @param lastWord The word to look up. + * * @return A list of gender strings (e.g., "masculine", "neuter"), or null if not a known noun. */ fun findGenderForLastWord( @@ -1504,8 +1532,10 @@ abstract class GeneralKeyboardIME( /** * Finds the next suggestions for the last typed word. + * * @param wordSuggestions The map of words to their suggestions. * @param lastWord The word to look up. + * * @return A list of gender strings (e.g., "masculine", "neuter"), or null if not a known noun. */ fun getNextWordSuggestions( @@ -1523,9 +1553,11 @@ abstract class GeneralKeyboardIME( /** * Checks if the last word is a known plural form. + * * @param pluralWords The set of all known plural words. * @param lastWord The word to check. - * @return `true` if the word is in the plural set, `false` otherwise. + * + * @return true if the word is in the plural set, false otherwise. */ fun findWhetherWordIsPlural( pluralWords: Set?, @@ -1534,8 +1566,10 @@ abstract class GeneralKeyboardIME( /** * Finds the required grammatical case(s) for a preposition. + * * @param caseAnnotation The map of prepositions to their required cases. * @param lastWord The word to look up (which should be a preposition). + * * @return A mutable list of case suggestions (e.g., "accusative case"), or null if not found. */ fun getCaseAnnotationForPreposition( @@ -1548,7 +1582,8 @@ abstract class GeneralKeyboardIME( /** * Updates the text of the suggestion buttons, primarily for displaying emoji suggestions. - * @param isAutoSuggestEnabled `true` if suggestions are active. + * + * @param isAutoSuggestEnabled true if suggestions are active. * @param autoSuggestEmojis The list of emojis to display. */ fun updateEmojiSuggestion( @@ -1600,8 +1635,9 @@ abstract class GeneralKeyboardIME( /** * The main dispatcher for displaying linguistic auto-suggestions (gender, case, plurality). + * * @param nounTypeSuggestion The detected gender(s) of the last word. - * @param isPlural `true` if the last word is plural. + * @param isPlural true if the last word is plural. * @param caseAnnotationSuggestion The detected case(s) required by the last word. */ fun updateAutoSuggestText( @@ -1686,8 +1722,10 @@ abstract class GeneralKeyboardIME( /** * A helper function to specifically trigger the plural suggestion UI if needed. - * @param isPlural `true` if the word is plural. - * @return `true` if the plural suggestion was handled, `false` otherwise. + * + * @param isPlural true if the word is plural. + * + * @return true if the plural suggestion was handled, false otherwise. */ private fun handlePluralIfNeeded(isPlural: Boolean): Boolean { if (isPlural) { @@ -1702,8 +1740,10 @@ abstract class GeneralKeyboardIME( /** * A helper function to handle displaying a single noun gender suggestion. + * * @param nounTypeSuggestion A list containing a single gender string. - * @return `true` if a suggestion was displayed, `false` otherwise. + * + * @return true if a suggestion was displayed, false otherwise. */ private fun handleSingleNounSuggestion(nounTypeSuggestion: List?): Boolean { if (nounTypeSuggestion?.size == 1 && !isSingularAndPlural) { @@ -1718,8 +1758,10 @@ abstract class GeneralKeyboardIME( /** * A helper function to handle displaying a single preposition case suggestion. + * * @param caseAnnotationSuggestion A list containing a single case annotation string. - * @return `true` if a suggestion was displayed, `false` otherwise. + * + * @return true if a suggestion was displayed, false otherwise. */ private fun handleSingleCaseSuggestion(caseAnnotationSuggestion: List?): Boolean { if (caseAnnotationSuggestion?.size == 1) { @@ -1739,8 +1781,10 @@ abstract class GeneralKeyboardIME( /** * A helper function to handle displaying multiple preposition case suggestions. + * * @param caseAnnotationSuggestion A list containing multiple case annotation strings. - * @return `true` if suggestions were displayed, `false` otherwise. + * + * @return true if suggestions were displayed, false otherwise. */ private fun handleMultipleCases(caseAnnotationSuggestion: List?): Boolean { if ((caseAnnotationSuggestion?.size ?: 0) > 1) { @@ -1753,9 +1797,11 @@ abstract class GeneralKeyboardIME( /** * Handles fallback logic when multiple suggestions are available but only one can be shown, * or when the primary suggestion type isn't displayable. + * * @param nounTypeSuggestion The list of noun suggestions. * @param caseAnnotationSuggestion The list of case suggestions. - * @return `true` if a fallback suggestion was applied, `false` otherwise. + * + * @return true if a fallback suggestion was applied, false otherwise. */ private fun handleFallbackSuggestions( nounTypeSuggestion: List?, @@ -1909,6 +1955,7 @@ abstract class GeneralKeyboardIME( /** * Configures a single suggestion button with the appropriate text and color based on the suggestion type. + * * @param singleTypeSuggestion The list containing the single suggestion to display. * @param type The type of suggestion, either "noun" or "preposition". */ @@ -1950,8 +1997,10 @@ abstract class GeneralKeyboardIME( /** * Determines the left and right suggestion types to display for dual suggestions. + * * @param type The suggestion type ("noun" or "preposition"). * @param suggestions The list of suggestion strings. + * * @return A pair of strings representing the left and right suggestion. */ private fun getSuggestionTypes( @@ -1966,8 +2015,10 @@ abstract class GeneralKeyboardIME( /** * Creates pairs of (color, text) for dual suggestion buttons. + * * @param type The suggestion type ("noun" or "preposition"). * @param suggestions The list of suggestion strings. + * * @return A pair of pairs, each containing a color resource ID and a text string, or null on failure. */ private fun getSuggestionPairs( @@ -1988,6 +2039,7 @@ abstract class GeneralKeyboardIME( /** * Applies a specific style to a suggestion button, including text, color, and a custom background. + * * @param button The Button to style. * @param colorRes The color resource ID for the background. * @param text The text to display on the button. @@ -2028,6 +2080,7 @@ abstract class GeneralKeyboardIME( /** * Sets up the UI for two side-by-side suggestion buttons. + * * @param leftSuggestion A pair containing the color and text for the left button. * @param rightSuggestion A pair containing the color and text for the right button. */ @@ -2059,6 +2112,7 @@ abstract class GeneralKeyboardIME( /** * Handles the logic when a word has multiple possible genders or * cases but only one suggestion slot is available. + * * It picks the first valid suggestion to display. * @param multipleTypeSuggestion The list of noun suggestions. */ @@ -2092,6 +2146,7 @@ abstract class GeneralKeyboardIME( /** * Handles the UI logic for displaying multiple suggestions simultaneously, * typically for words with multiple genders. + * * @param multipleTypeSuggestion The list of suggestions to display. * @param type The type of suggestion, either "noun" or "preposition". */ @@ -2153,6 +2208,7 @@ abstract class GeneralKeyboardIME( /** * Sets the text size and color for a default, non-active suggestion button. + * * @param button The button to style. */ @@ -2164,7 +2220,9 @@ abstract class GeneralKeyboardIME( /** * Retrieves the plural form of a word from the database. + * * @param word The singular word to find the plural for. + * * @return The plural form as a string, or null if not found. */ private fun getPluralRepresentation(word: String?): String? { @@ -2186,7 +2244,8 @@ abstract class GeneralKeyboardIME( /** * Moves the cursor in the input field. - * @param moveRight `true` to move right, `false` to move left. + * + * @param moveRight true to move right, false to move left. */ private fun moveCursor(moveRight: Boolean) { val extractedText = currentInputConnection?.getExtractedText(ExtractedTextRequest(), 0) ?: return @@ -2196,8 +2255,10 @@ abstract class GeneralKeyboardIME( /** * Retrieves the translation for a given word. + * * @param language The current keyboard language (destination language). * @param commandBarInput The word to be translated (source word). + * * @return The translated word as a string. */ private fun getTranslation( @@ -2210,6 +2271,7 @@ abstract class GeneralKeyboardIME( /** * Gets the IME action ID (e.g., Go, Search, Done) from the current editor info. + * * @return The IME action ID, or `IME_ACTION_NONE`. */ private fun getImeOptionsActionId(): Int = @@ -2252,7 +2314,8 @@ abstract class GeneralKeyboardIME( } /** - * Handles the Enter key press when in the `PLURAL` or `TRANSLATE` state. + * Handles the Enter key press when in the plural or translate state. + * * @param rawInput The text from the command bar. * @param inputConnection The current input connection. */ @@ -2288,6 +2351,7 @@ abstract class GeneralKeyboardIME( /** * Handles the Enter key press when in the `CONJUGATE` state. It fetches the * conjugation data for the entered verb and transitions to the selection view. + * * @param rawInput The verb entered in the command bar. */ private fun handleConjugateState(rawInput: String) { @@ -2328,7 +2392,9 @@ abstract class GeneralKeyboardIME( /** * Handles the default behavior of the Enter key when not in a special Scribe command mode. + * * It performs the editor action or sends a standard Enter key event. + * * @param inputConnection The current input connection. */ private fun handleDefaultEnter(inputConnection: InputConnection) { @@ -2346,6 +2412,7 @@ abstract class GeneralKeyboardIME( /** * Commits the output of a Scribe command (like translation or pluralization) to the input field. + * * @param commandModeOutput The string result of the command. * @param inputConnection The current input connection. */ @@ -2364,6 +2431,7 @@ abstract class GeneralKeyboardIME( /** * Handles switching between the letter and symbol keyboards. + * * @param keyboardMode The current keyboard mode (letters or symbols). * @param keyboardView The instance of the keyboard view. * @param context The application context. @@ -2394,6 +2462,7 @@ abstract class GeneralKeyboardIME( /** * Handles the logic for the Shift key. It cycles through shift states (off, on-for-one-char, caps lock) * on the letter keyboard, and toggles between symbol pages on the symbol keyboard. + * * @param keyboardMode The current keyboard mode. * @param keyboardView The instance of the keyboard view. */ @@ -2475,8 +2544,10 @@ abstract class GeneralKeyboardIME( /** * Handles a key press on one of the special conjugation keys. * It either commits the text directly or prepares for a subsequent selection view. + * * @param code The key code of the pressed key. - * @param isSubsequentRequired `true` if a sub-view is needed for more options. + * @param isSubsequentRequired true if a sub-view is needed for more options. + * * @return The label of the key that was pressed. */ fun handleConjugateKeys( @@ -2496,8 +2567,9 @@ abstract class GeneralKeyboardIME( /** * Handles the logic for the Delete/Backspace key. It deletes characters from either * the main input field or the command bar, depending on the context. - * @param isCommandBar `true` if the deletion should happen in the command bar. - * @param isLongPress `true` if this is a long press/repeat action, `false` for single tap. + * + * @param isCommandBar true` if the deletion should happen in the command bar. + * @param isLongPress true` if this is a long press/repeat action, false for single tap. */ fun handleDelete( isCommandBar: Boolean = false, @@ -2535,6 +2607,7 @@ abstract class GeneralKeyboardIME( /** * Deletes an entire word, including any trailing whitespace. + * * @param inputConnection The current input connection. */ private fun deleteWordByWord(inputConnection: InputConnection) { @@ -2606,9 +2679,10 @@ abstract class GeneralKeyboardIME( /** * Handles the input of any non-special character key (e.g., letters, numbers, punctuation). * It commits the character to the main input field or the command bar. + * * @param code The character code of the key. * @param keyboardMode The current keyboard mode. - * @param commandBarState `true` if input should go to the command bar. + * @param commandBarState true if input should go to the command bar. */ fun handleElseCondition( code: Int, @@ -2661,6 +2735,7 @@ abstract class GeneralKeyboardIME( /** * Gets the current text in the command bar without the cursor. + * * @return The text content without the trailing cursor character. */ private fun getCommandBarTextWithoutCursor(): String { @@ -2674,6 +2749,7 @@ abstract class GeneralKeyboardIME( /** * Sets the command bar text and ensures it ends with the custom cursor. + * * @param text The text to set (without cursor). * @param cursorAtStart The flag to check if the text in the EditText is empty to determine the position of the cursor */ diff --git a/app/src/main/java/be/scri/ui/common/appcomponents/ConfirmationDialog.kt b/app/src/main/java/be/scri/ui/common/appcomponents/ConfirmationDialog.kt new file mode 100644 index 00000000..110599ad --- /dev/null +++ b/app/src/main/java/be/scri/ui/common/appcomponents/ConfirmationDialog.kt @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +package be.scri.ui.common.appcomponents + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonColors +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.em +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import be.scri.R +import be.scri.helpers.PreferencesHelper.getIsDarkModeOrNot + +/** + * A confirmation dialog with customizable text and actions. + * + * @param text The main text to display in the dialog. + * @param textChange The text for the change button. + * @param textConfirm The text for the confirm button. + * @param modifier Modifier for layout and styling. + * @param onConfirm Callback triggered when the confirm button is clicked. + * @param onChange Callback triggered when the change button is clicked. + * @param onDismiss Callback triggered when the dialog is dismissed. + */ +@Composable +fun ConfirmationDialog( + text: String, + textChange: String, + textConfirm: String, + modifier: Modifier = Modifier, + onConfirm: () -> Unit = {}, + onChange: () -> Unit = {}, + onDismiss: () -> Unit = {}, +) { + val context = LocalContext.current + val isUserDarkMode = remember { getIsDarkModeOrNot(context.applicationContext) } + + ConfirmationDialogContent( + text = text, + textConfirm = textConfirm, + textChange = textChange, + onConfirm = onConfirm, + onChange = onChange, + isUserDarkMode = isUserDarkMode, + modifier = modifier.padding(top = 24.dp), + onDismiss = onDismiss, + ) +} + +/** + * The content of the confirmation dialog. + * + * @param text The main text to display in the dialog. + * @param textConfirm The text for the confirm button. + * @param textChange The text for the change button. + * @param isUserDarkMode Whether the dark mode is enabled. + * @param modifier Modifier for layout and styling. + * @param onConfirm Callback triggered when the confirm button is clicked. + * @param onChange Callback triggered when the change button is clicked. + * @param onDismiss Callback triggered when the dialog is dismissed. + */ +@Composable +fun ConfirmationDialogContent( + text: String, + onConfirm: () -> Unit, + onChange: () -> Unit, + isUserDarkMode: Boolean, + textConfirm: String, + textChange: String, + modifier: Modifier = Modifier, + onDismiss: () -> Unit, +) { + val buttonConfirmColor: Color + val buttonChangeColor: Color + val buttonTextColor: Color + val textFontSize = 16.sp + val buttonPadding = 8.dp + if (isUserDarkMode) { + buttonConfirmColor = colorResource(R.color.dark_scribe_blue) + buttonChangeColor = colorResource(R.color.dark_special_key_color) + buttonTextColor = Color.White + } else { + buttonConfirmColor = colorResource(R.color.light_scribe_blue) + buttonChangeColor = colorResource(R.color.light_special_key_color) + buttonTextColor = Color.Black + } + + val shadowColor = colorResource(R.color.light_key_shadow_color) + + Dialog( + onDismissRequest = { + onDismiss() + }, + properties = + DialogProperties( + usePlatformDefaultWidth = false, + ), + ) { + Box( + modifier = + modifier + .fillMaxWidth(0.95f) + .padding(horizontal = 4.dp) + .background( + brush = + Brush.verticalGradient( + colors = + listOf( + Color.Transparent, + shadowColor, + ), + ), + shape = RoundedCornerShape(10.dp), + ), + ) { + Surface( + shape = RoundedCornerShape(10.dp), + color = MaterialTheme.colorScheme.surface, + shadowElevation = 4.dp, + modifier = Modifier.padding(bottom = 4.dp), + ) { + Column( + modifier = Modifier.padding(10.dp), + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(vertical = 8.dp, horizontal = 6.dp), + ) { + Icon( + painter = painterResource(R.drawable.ic_info_vector), + contentDescription = "Confirmation", + tint = Color(0xFFFDAD0D), + modifier = + Modifier + .padding(start = 0.dp, end = 10.dp) + .size(60.dp), + ) + + Text( + text = text, + color = MaterialTheme.colorScheme.onSurface, + fontSize = textFontSize, + style = + MaterialTheme.typography.bodyMedium.merge( + TextStyle(lineHeight = 1.5.em), + ), + modifier = Modifier.weight(0.85f), + ) + } + Row( + horizontalArrangement = Arrangement.End, + modifier = Modifier.fillMaxWidth(), + ) { + Box( + modifier = + Modifier + .padding(end = 2.dp) + .background(Color.Transparent) + .drawBehind { + drawRoundRect( + color = shadowColor, + topLeft = Offset.Zero.copy(y = size.height - 16.dp.toPx()), + size = size.copy(height = 14.dp.toPx()), + cornerRadius = CornerRadius(8.dp.toPx()), + ) + }, + ) { + Button( + onClick = onChange, + colors = + ButtonColors( + containerColor = buttonChangeColor, + contentColor = buttonTextColor, + disabledContainerColor = MaterialTheme.colorScheme.secondary, + disabledContentColor = Color.White, + ), + contentPadding = PaddingValues(buttonPadding), + shape = RoundedCornerShape(8.dp), + ) { + Text( + text = textChange, + fontSize = textFontSize, + modifier = Modifier, + ) + } + } + + Spacer(modifier = Modifier.width(10.dp)) + + Box( + modifier = + Modifier + .background(Color.Transparent) + .drawBehind { + drawRoundRect( + color = shadowColor, + topLeft = Offset.Zero.copy(y = size.height - 16.dp.toPx()), + size = size.copy(height = 14.dp.toPx()), + cornerRadius = CornerRadius(8.dp.toPx()), + ) + }, + ) { + Button( + onClick = onConfirm, + colors = + ButtonColors( + containerColor = buttonConfirmColor, + contentColor = buttonTextColor, + disabledContainerColor = MaterialTheme.colorScheme.secondary, + disabledContentColor = Color.White, + ), + contentPadding = PaddingValues(buttonPadding), + shape = RoundedCornerShape(8.dp), + ) { + Text( + text = textConfirm, + fontSize = textFontSize, + modifier = Modifier, + ) + } + } + } + } + } + } + } +} diff --git a/app/src/main/java/be/scri/ui/common/components/DownloadDataOptionComp.kt b/app/src/main/java/be/scri/ui/common/components/DownloadDataOptionComp.kt index 446fa13f..650c4a84 100644 --- a/app/src/main/java/be/scri/ui/common/components/DownloadDataOptionComp.kt +++ b/app/src/main/java/be/scri/ui/common/components/DownloadDataOptionComp.kt @@ -16,10 +16,6 @@ import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -28,7 +24,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import be.scri.R -import java.time.LocalDate +import be.scri.ui.screens.download.DownloadState /** * A button component that reflects the state of a data download. @@ -40,10 +36,11 @@ import java.time.LocalDate */ @Composable fun DownloadDataOptionComp( + onClick: () -> Unit, isDarkTheme: Boolean, modifier: Modifier = Modifier, + downloadState: DownloadState = DownloadState.Ready, ) { - var downloadState by remember { mutableStateOf(DownloadState.Ready) } val colorScheme = MaterialTheme.colorScheme val backgroundColor = getBackgroundColor(downloadState, isDarkTheme, colorScheme) @@ -51,20 +48,7 @@ fun DownloadDataOptionComp( val iconColor = getIconColor(isDarkTheme, textColor) Button( - onClick = { - downloadState = - when (downloadState) { - DownloadState.Ready -> DownloadState.Downloading - DownloadState.Downloading -> DownloadState.Completed - DownloadState.Completed -> - if (isUpdateAvailable(PLACEBO_LOCAL_UPDATED_AT, PLACEBO_SERVER_UPDATED_AT)) { - DownloadState.Update - } else { - DownloadState.Completed - } - DownloadState.Update -> DownloadState.Downloading - } - }, + onClick = onClick, enabled = true, colors = ButtonDefaults.buttonColors( @@ -191,29 +175,3 @@ private fun DownloadStateIcon( } } } - -/** - * @return true if server data is newer than local data - */ -private const val PLACEBO_SERVER_UPDATED_AT = "2025-01-10" -private const val PLACEBO_LOCAL_UPDATED_AT = "2025-01-01" - -private fun isUpdateAvailable( - localUpdatedAt: String, - serverUpdatedAt: String, -): Boolean { - val localDate = LocalDate.parse(localUpdatedAt) - val serverDate = LocalDate.parse(serverUpdatedAt) - - return serverDate.isAfter(localDate) -} - -/** - * Represents the state of the download button. - */ -enum class DownloadState { - Ready, - Downloading, - Completed, - Update, -} diff --git a/app/src/main/java/be/scri/ui/common/components/LanguageItemComp.kt b/app/src/main/java/be/scri/ui/common/components/LanguageItemComp.kt index 9810211b..168d3faf 100644 --- a/app/src/main/java/be/scri/ui/common/components/LanguageItemComp.kt +++ b/app/src/main/java/be/scri/ui/common/components/LanguageItemComp.kt @@ -19,6 +19,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import be.scri.ui.screens.download.DownloadState /** * Simple language item with a title on the left and a download button on the right. @@ -27,10 +28,12 @@ import androidx.compose.ui.unit.sp fun LanguageItemComp( title: String, onClick: () -> Unit, + onButtonClick: () -> Unit, modifier: Modifier = Modifier, titleFontWeight: FontWeight = FontWeight.Normal, titleFontSize: androidx.compose.ui.unit.TextUnit = 16.sp, isDarkTheme: Boolean = false, + buttonState: DownloadState? = null, ) { Row( modifier = @@ -52,7 +55,9 @@ fun LanguageItemComp( ) Spacer(modifier = Modifier.width(100.dp)) DownloadDataOptionComp( + onClick = onButtonClick, isDarkTheme = isDarkTheme, + downloadState = buttonState ?: DownloadState.Ready, modifier = Modifier.widthIn(min = 50.dp), ) } 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 1ae39df7..58f19de0 100644 --- a/app/src/main/java/be/scri/ui/screens/LanguageSettingsScreen.kt +++ b/app/src/main/java/be/scri/ui/screens/LanguageSettingsScreen.kt @@ -395,6 +395,7 @@ private fun getLayoutListData( * If the specified language is not recognized, defaults to the English string resource. * * @param language The name of the language (e.g., "German", "French"). + * * @return The string resource ID corresponding to the localized name. */ fun getLanguageStringFromi18n(language: String): Int { @@ -418,6 +419,7 @@ fun getLanguageStringFromi18n(language: String): Int { * When the user selects the source language item, the provided [onTranslationLanguageSelect] callback is triggered. * * @param onTranslationLanguageSelect A lambda function invoked when the user clicks the "Select Source Language" item. + * * @return A list of [ScribeItem]s to be displayed in the UI. */ @Composable diff --git a/app/src/main/java/be/scri/ui/screens/SelectLanguageScreen.kt b/app/src/main/java/be/scri/ui/screens/SelectLanguageScreen.kt index 83c3b7c3..40e2504f 100644 --- a/app/src/main/java/be/scri/ui/screens/SelectLanguageScreen.kt +++ b/app/src/main/java/be/scri/ui/screens/SelectLanguageScreen.kt @@ -30,27 +30,38 @@ import androidx.compose.ui.unit.dp import androidx.core.content.edit import be.scri.R import be.scri.ui.common.ScribeBaseScreen +import be.scri.ui.common.appcomponents.ConfirmationDialog /** * The Select Languages subpage is for selecting the translation source language. + * @param currentLanguage The current main/destination language. + * @param onBackNavigation Callback for back navigation action. + * @param onNavigateToDownloadData Callback for navigating to the data download screen. + * @param modifier Modifier for layout and styling. + * @param onDownloadAction Callback for download action when a new source language is selected and confirmed. */ @Composable fun SelectTranslationSourceLanguageScreen( currentLanguage: String, onBackNavigation: () -> Unit, + onNavigateToDownloadData: () -> Unit, modifier: Modifier = Modifier, + onDownloadAction: (String) -> Unit = {}, ) { val context = LocalContext.current val sharedPref = context.getSharedPreferences("app_preferences", Context.MODE_PRIVATE) - var selectedLanguage = + var savedLanguage = remember { mutableStateOf(sharedPref.getString("translation_source_$currentLanguage", "English") ?: "English") } + var selectedLanguage = remember { mutableStateOf(savedLanguage.value) } + var showDialog = remember { mutableStateOf(false) } val scrollState = rememberScrollState() val options = listOf("English", "French", "German", "Italian", "Portuguese", "Russian", "Spanish", "Swedish") .filterNot { it == getDisplayLanguageName(currentLanguage) } + ScribeBaseScreen( pageTitle = stringResource(R.string.app_settings_keyboard_translation_select_source_title), lastPage = stringResource(id = getLanguageStringFromi18n(currentLanguage)), @@ -84,8 +95,11 @@ fun SelectTranslationSourceLanguageScreen( Modifier .fillMaxWidth() .clickable { - selectedLanguage.value = option - sharedPref.edit { putString("translation_source_$currentLanguage", option) } + // Only show dialog if the selection is different + if (option != savedLanguage.value) { + selectedLanguage.value = option + showDialog.value = true + } }.padding(vertical = 5.dp, horizontal = 8.dp), ) { Text( @@ -96,8 +110,11 @@ fun SelectTranslationSourceLanguageScreen( RadioButton( selected = (option == selectedLanguage.value), onClick = { - selectedLanguage.value = option - sharedPref.edit { putString("translation_source_$currentLanguage", option) } + // Only show dialog if the selection is different + if (option != savedLanguage.value) { + selectedLanguage.value = option + showDialog.value = true + } }, ) } @@ -110,6 +127,39 @@ fun SelectTranslationSourceLanguageScreen( } } } + + if (showDialog.value) { + ConfirmationDialog( + text = + "You've changed your source translation language. " + + "Would you like to download new data so that you can translate " + + "from ${selectedLanguage.value}?", + textConfirm = "Download data", + textChange = "Keep ${savedLanguage.value}", + onConfirm = { + // User confirmed - save the new selection permanently. + savedLanguage.value = selectedLanguage.value + sharedPref.edit { putString("translation_source_$currentLanguage", selectedLanguage.value) } + + val downloadKey = currentLanguage.lowercase() + // trigger the download action in the ViewModel. + onDownloadAction(downloadKey) + showDialog.value = false + // Navigate to the download data screen. + onNavigateToDownloadData() + }, + onChange = { + // User cancelled - revert back to old selection. + selectedLanguage.value = savedLanguage.value + showDialog.value = false + }, + onDismiss = { + // Dialog dismissed - revert back to old selection. + selectedLanguage.value = savedLanguage.value + showDialog.value = false + }, + ) + } } @Composable diff --git a/app/src/main/java/be/scri/ui/screens/about/AboutUtil.kt b/app/src/main/java/be/scri/ui/screens/about/AboutUtil.kt index ed09bd38..8ccfe669 100644 --- a/app/src/main/java/be/scri/ui/screens/about/AboutUtil.kt +++ b/app/src/main/java/be/scri/ui/screens/about/AboutUtil.kt @@ -207,6 +207,7 @@ object AboutUtil { * @param onWikimediaAndScribeClick Callback invoked when Wikimedia link is clicked. * @param onShareScribeClick Callback invoked when Share Scribe link is clicked. * @param context Android context to open URLs. + * * @return A [ScribeItemList] wrapping community external links. */ @Composable @@ -228,6 +229,7 @@ object AboutUtil { * @param onMailClick Callback to open email intent. * @param onResetHintsClick Callback to reset onboarding hints. * @param context Android context used to launch external intents. + * * @return A [ScribeItemList] wrapping feedback and support options. */ @Composable @@ -254,6 +256,7 @@ object AboutUtil { * * @param onPrivacyPolicyClick Callback invoked when Privacy Policy is selected. * @param onThirdPartyLicensesClick Callback invoked when Third-Party Licenses is selected. + * * @return A [ScribeItemList] wrapping legal information items. */ @Composable diff --git a/app/src/main/java/be/scri/ui/screens/DataDownloadScreen.kt b/app/src/main/java/be/scri/ui/screens/download/DataDownloadScreen.kt similarity index 73% rename from app/src/main/java/be/scri/ui/screens/DataDownloadScreen.kt rename to app/src/main/java/be/scri/ui/screens/download/DataDownloadScreen.kt index 6023d742..63f99a0a 100644 --- a/app/src/main/java/be/scri/ui/screens/DataDownloadScreen.kt +++ b/app/src/main/java/be/scri/ui/screens/download/DataDownloadScreen.kt @@ -1,7 +1,8 @@ // SPDX-License-Identifier: GPL-3.0-or-later -package be.scri.ui.screens +package be.scri.ui.screens.download +import android.content.Context import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -28,20 +29,35 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import be.scri.R import be.scri.ui.common.ScribeBaseScreen +import be.scri.ui.common.appcomponents.ConfirmationDialog import be.scri.ui.common.components.CircleClickableItemComp import be.scri.ui.common.components.LanguageItemComp import be.scri.ui.common.components.SwitchableItemComp import be.scri.ui.screens.settings.SettingsUtil +/** + * Screen for downloading and managing language data. + * + * @param onBackNavigation Callback for back navigation action. + * @param onNavigateToTranslation Callback for navigating to translation language selection. + * @param modifier Modifier for layout and styling. + * @param downloadStates Map of language keys to their download states. + * @param onDownloadAction Callback for download action when a language is selected and confirmed. + */ @Composable fun DownloadDataScreen( onBackNavigation: () -> Unit, + onNavigateToTranslation: (String) -> Unit, modifier: Modifier = Modifier, + downloadStates: Map = emptyMap(), + onDownloadAction: (String) -> Unit = {}, ) { val scrollState = rememberScrollState() val checkForNewData = remember { mutableStateOf(false) } val regularlyUpdateData = remember { mutableStateOf(true) } + val selectedLanguage = remember { mutableStateOf?>(null) } val context = LocalContext.current + val sharedPref = context.getSharedPreferences("app_preferences", Context.MODE_PRIVATE) val installedKeyboardLanguages = remember { SettingsUtil.getKeyboardLanguages(context) @@ -140,12 +156,22 @@ fun DownloadDataScreen( color = MaterialTheme.colorScheme.surface, ) { Column(Modifier.padding(vertical = 10.dp, horizontal = 4.dp)) { - languages.forEachIndexed { index, (key, title, isDark) -> + languages.forEachIndexed { index, lang -> + val (key, title, isDark) = lang + val currentStatus = downloadStates[key] ?: DownloadState.Ready + LanguageItemComp( title = title, - onClick = { + onClick = { }, + onButtonClick = { + if (currentStatus == DownloadState.Ready) { + selectedLanguage.value = lang + } else { + onDownloadAction(key) + } }, isDarkTheme = isDark, + buttonState = currentStatus, ) if (index < languages.lastIndex) { HorizontalDivider( @@ -160,6 +186,25 @@ fun DownloadDataScreen( } Spacer(modifier = Modifier.height(10.dp)) + + selectedLanguage.value?.let { lang -> + val (key, title, _) = lang + val languageId = key.replaceFirstChar { it.uppercase() } + val sourceLang = sharedPref.getString("translation_source_$languageId", "English") ?: "English" + ConfirmationDialog( + text = + "The data you will download will allow you to translate from $sourceLang to $title." + + " Do you want to change the language you'll translate from?", + textConfirm = "Use $sourceLang", + textChange = "Change language", + onConfirm = { + onDownloadAction(key) + selectedLanguage.value = null + }, + onChange = { onNavigateToTranslation(languageId) }, + onDismiss = { selectedLanguage.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 new file mode 100644 index 00000000..e3230e5d --- /dev/null +++ b/app/src/main/java/be/scri/ui/screens/download/DataDownloadViewModel.kt @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package be.scri.ui.screens.download + +import androidx.compose.runtime.mutableStateMapOf +import androidx.lifecycle.ViewModel +import java.time.LocalDate + +private const val PLACEBO_SERVER_UPDATED_AT = "2025-01-10" +private const val PLACEBO_LOCAL_UPDATED_AT = "2025-01-01" + +/** ViewModel to manage data download states and actions. */ +class DataDownloadViewModel : ViewModel() { + val downloadStates = mutableStateMapOf() + + /** + * @return true if server data is newer than local data. + */ + private fun isUpdateAvailable( + localUpdatedAt: String, + serverUpdatedAt: String, + ): Boolean { + val localDate = LocalDate.parse(localUpdatedAt) + val serverDate = LocalDate.parse(serverUpdatedAt) + + return serverDate.isAfter(localDate) + } + + /** + * Handles the download action based on the current state. + * + * @param key The key identifying the download item. + */ + fun handleDownloadAction(key: String) { + val currentState = downloadStates[key] ?: DownloadState.Ready + downloadStates[key] = + when (currentState) { + DownloadState.Ready -> DownloadState.Downloading + DownloadState.Downloading -> DownloadState.Completed + DownloadState.Completed -> + if (isUpdateAvailable(PLACEBO_LOCAL_UPDATED_AT, PLACEBO_SERVER_UPDATED_AT)) { + DownloadState.Update + } else { + DownloadState.Completed + } + DownloadState.Update -> DownloadState.Downloading + } + } +} + +/** + * Represents the state of the download button. + */ +enum class DownloadState { + Ready, + Downloading, + Completed, + Update, +} diff --git a/app/src/main/java/be/scri/ui/screens/settings/SettingsUtil.kt b/app/src/main/java/be/scri/ui/screens/settings/SettingsUtil.kt index a1cdcdeb..17359777 100644 --- a/app/src/main/java/be/scri/ui/screens/settings/SettingsUtil.kt +++ b/app/src/main/java/be/scri/ui/screens/settings/SettingsUtil.kt @@ -20,7 +20,8 @@ object SettingsUtil { * Checks whether the custom keyboard is already installed and enabled. * * @param context The context to access system services. - * @return True if the keyboard is installed, false otherwise. + * + * @return true if the keyboard is installed, false otherwise. */ fun checkKeyboardInstallation(context: Context): Boolean { val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager @@ -31,7 +32,7 @@ object SettingsUtil { /** * Sets the app theme to light or dark mode and saves the user's preference. * - * @param isDarkMode True to enable dark mode, false for light mode. + * @param isDarkMode true to enable dark mode, false for light mode. * @param context The context used to save preferences. */ fun setLightDarkMode( @@ -87,6 +88,7 @@ object SettingsUtil { * Retrieves the list of available keyboard languages based on enabled input methods. * * @param context The context to access input methods. + * * @return A list of language names. */ fun getKeyboardLanguages(context: Context): List { @@ -110,6 +112,7 @@ object SettingsUtil { * Maps a language name to its corresponding localized string resource ID. * * @param language The name of the language. + * * @return The string resource ID for the localized name. */ fun getLocalizedLanguageName(language: String): Int { diff --git a/app/src/main/java/be/scri/ui/screens/settings/SettingsViewModel.kt b/app/src/main/java/be/scri/ui/screens/settings/SettingsViewModel.kt index 2c4437d3..e631e55a 100644 --- a/app/src/main/java/be/scri/ui/screens/settings/SettingsViewModel.kt +++ b/app/src/main/java/be/scri/ui/screens/settings/SettingsViewModel.kt @@ -52,7 +52,7 @@ class SettingsViewModel( /** * Updates the UI theme mode based on the user's preference. * - * @param value True to enable dark mode, false for light mode. + * @param value true to enable dark mode, false for light mode. */ fun setLightDarkMode(value: Boolean) { _isUserDarkMode.value = value diff --git a/app/src/main/java/be/scri/views/KeyboardView.kt b/app/src/main/java/be/scri/views/KeyboardView.kt index b09661be..085fe80d 100644 --- a/app/src/main/java/be/scri/views/KeyboardView.kt +++ b/app/src/main/java/be/scri/views/KeyboardView.kt @@ -92,14 +92,16 @@ class KeyboardView /** * Called when the user presses a key. This is sent before the [.onKey] is called. * For keys that repeat, this is only called once. - * @param primaryCode the unicode of the key being pressed. + * + * @param primaryCode The unicode of the key being pressed. * If the touch is not on a valid key, the value will be zero. */ fun onPress(primaryCode: Int) /** * Send a key press to the listener. - * @param code this is the key that was pressed + * + * @param code The key that was pressed */ fun onKey(code: Int) @@ -120,12 +122,14 @@ class KeyboardView /** * Sends a sequence of characters to the listener. - * @param text the string to be displayed. + * + * @param text The string to be displayed. */ fun onText(text: String) /** * Checks if there is text before the current cursor position. + * * @return true if there is text before the cursor and false otherwise. */ fun hasTextBeforeCursor(): Boolean @@ -331,6 +335,7 @@ class KeyboardView /** * Sets the color of the Enter key based on a specific color or theme mode. + * * @param color The optional color to apply. * @param isDarkMode Whether the dark mode is enabled (optional). */ @@ -357,8 +362,10 @@ class KeyboardView /** * Sets the icon of the Enter key based on current state. + * * @param state The current keyboard state. * @param earlierValue Previously assigned Enter key value (optional). + * * @return The updated Enter key value. */ fun setEnterKeyIcon( @@ -455,6 +462,7 @@ class KeyboardView * Returns the label for a key with the given code. * * @param code The code of the key. + * * @return The label for the key, or null if the key code is not recognized. */ fun getKeyLabel(code: Int): String? = @@ -624,6 +632,7 @@ class KeyboardView /** * Attaches a keyboard to this view. * The keyboard can be switched at any time and the view will re-layout itself to accommodate the keyboard. + * * @param keyboard the keyboard to display in this view */ fun setKeyboard(keyboard: KeyboardBase) { @@ -674,8 +683,10 @@ class KeyboardView /** * Sets the state of the shift key of the keyboard, if any. - * @param shifted whether or not to enable the state of the shift key - * @return true if the shift key state changed, false if there was no change + * + * @param shifted Whether or not to enable the state of the shift key + * + * @return true if the shift key state changed, false if there was no change. */ private fun setShifted(shiftState: Int) { if (mKeyboard?.setShifted(shiftState) == true) { @@ -685,7 +696,7 @@ class KeyboardView /** * Returns the state of the shift key of the keyboard, if any. - * @return true if the shift is in a pressed state, false otherwise + * @return true if the shift is in a pressed state, false otherwise. */ private fun isShifted(): Boolean = mKeyboard?.mShiftState ?: SHIFT_OFF > SHIFT_OFF @@ -738,7 +749,8 @@ class KeyboardView * Compute the average distance between adjacent keys (horizontally and vertically) * and square it to get the proximity threshold. * We use a square here and in computing the touch distance from a key's center to avoid taking a square root. - * @param keyboard + * + * @param keyboard The base class for the keyboard UI. */ private fun computeProximityThreshold(keyboard: KeyboardBase?) { if (keyboard == null) { @@ -1348,7 +1360,8 @@ class KeyboardView * Invalidates a key so that it will be redrawn on the next repaint. * Use this method if only one key is changing it's content. Any changes that * affect the position or size of the key may not be honored. - * @param keyIndex the index of the key in the attached [KeyboardBase]. + * + * @param keyIndex The index of the key in the attached [KeyboardBase]. */ private fun invalidateKey(keyIndex: Int) { if (keyIndex < 0 || keyIndex >= mKeys.size) { @@ -1390,7 +1403,9 @@ class KeyboardView * Called when a key is long pressed. * By default this will open any popup keyboard associated with this key through the attributes * popupLayout and popupCharacters. - * @param popupKey the key that was long pressed + * + * @param popupKey The key that was long pressed. + * * @return true if the long press is handled, false otherwise. * Subclasses should call the method on the base class if the subclass doesn't wish to * handle the call.