Skip to content

Commit 4109101

Browse files
Merge branch 'main' into keyboard
2 parents 268c84a + 3e8b09d commit 4109101

16 files changed

Lines changed: 613 additions & 38 deletions

File tree

app/src/keyboards/java/be/scri/helpers/KeyHandler.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ import be.scri.services.GeneralKeyboardIME
2020
class KeyHandler(
2121
private val ime: GeneralKeyboardIME,
2222
) {
23-
private val suggestionHandler = SuggestionHandler(ime)
23+
private val suggestionHandler = ime.suggestionHandler
2424
private val spaceKeyProcessor = SpaceKeyProcessor(ime, suggestionHandler)
25-
private val autocompletionHandler = AutocompletionHandler(ime)
25+
private val autocompletionHandler = ime.autocompletionHandler
2626

2727
/** Tracks if the last key pressed was a space, used for "period on double space" logic. */
2828
private var wasLastKeySpace: Boolean = false

app/src/keyboards/java/be/scri/helpers/ui/KeyboardUIManager.kt

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,17 @@ class KeyboardUIManager(
6262

6363
fun getKeyboardLayoutXML(): Int
6464

65+
fun getCurrentKeyboardLayoutXML(): Int
66+
6567
fun getCurrentEnterKeyType(): Int
6668

6769
fun commitText(text: String)
6870

6971
fun onKeyboardActionListener(): KeyboardView.OnKeyboardActionListener
7072

7173
fun processLinguisticSuggestions(word: String)
74+
75+
fun isNumericKeyboardActive(): Boolean
7276
}
7377

7478
var keyboardView: KeyboardView = binding.keyboardView
@@ -159,7 +163,12 @@ class KeyboardUIManager(
159163
val isUserDarkMode = getIsDarkModeOrNot(context)
160164

161165
when (currentState) {
162-
ScribeState.IDLE -> setupIdleView(language, emojiAutoSuggestionEnabled, autoSuggestEmojis)
166+
ScribeState.IDLE ->
167+
setupIdleView(
168+
language,
169+
emojiAutoSuggestionEnabled,
170+
autoSuggestEmojis,
171+
)
163172
ScribeState.SELECT_COMMAND -> setupSelectCommandView(language)
164173
ScribeState.INVALID -> setupInvalidView(language, invalidCommandSource)
165174
ScribeState.TRANSLATE -> {
@@ -184,7 +193,7 @@ class KeyboardUIManager(
184193
emojiAutoSuggestionEnabled: Boolean,
185194
autoSuggestEmojis: MutableList<String>?,
186195
) {
187-
binding.commandOptionsBar.visibility = View.VISIBLE
196+
binding.commandOptionsBar.visibility = if (listener.isNumericKeyboardActive()) View.GONE else View.VISIBLE
188197
binding.toolbarBar.visibility = View.GONE
189198

190199
val isUserDarkMode = getIsDarkModeOrNot(context)
@@ -226,7 +235,11 @@ class KeyboardUIManager(
226235

227236
binding.scribeKeyOptions.foreground = AppCompatResources.getDrawable(context, R.drawable.ic_scribe_icon_vector)
228237

229-
initializeKeyboard(listener.getKeyboardLayoutXML())
238+
val keyboardXml = listener.getCurrentKeyboardLayoutXML()
239+
initializeKeyboard(keyboardXml)
240+
if (keyboardXml == R.xml.keys_symbols) {
241+
setupCurrencySymbol(language)
242+
}
230243

231244
updateButtonVisibility(ScribeState.IDLE, emojiAutoSuggestionEnabled, autoSuggestEmojis)
232245
updateEmojiSuggestion(ScribeState.IDLE, emojiAutoSuggestionEnabled, autoSuggestEmojis)
@@ -239,7 +252,7 @@ class KeyboardUIManager(
239252
* (Translate, Conjugate, Plural).
240253
*/
241254
private fun setupSelectCommandView(language: String) {
242-
binding.commandOptionsBar.visibility = View.VISIBLE
255+
binding.commandOptionsBar.visibility = if (listener.isNumericKeyboardActive()) View.GONE else View.VISIBLE
243256
binding.toolbarBar.visibility = View.GONE
244257

245258
val isUserDarkMode = getIsDarkModeOrNot(context)
@@ -786,6 +799,8 @@ class KeyboardUIManager(
786799
* Disables all auto-suggestions and resets the suggestion buttons to their default, inactive state.
787800
*/
788801
fun disableAutoSuggest(language: String) {
802+
val isNumericKeyboard = listener.getCurrentKeyboardLayoutXML() == R.xml.keys_numeric
803+
789804
binding.translateBtnRight.visibility = View.INVISIBLE
790805
binding.translateBtnLeft.visibility = View.INVISIBLE
791806
binding.translateBtn.visibility = View.VISIBLE
@@ -801,9 +816,20 @@ class KeyboardUIManager(
801816
binding.translateBtn.background = null
802817
binding.translateBtn.setOnClickListener(createSuggestionClickListener(suggestion1))
803818

804-
val suggestion2 = suggestions.getOrNull(1) ?: ""
805-
binding.conjugateBtn.text = suggestion2
806-
binding.conjugateBtn.setOnClickListener(createSuggestionClickListener(suggestion2))
819+
if (isNumericKeyboard) {
820+
binding.conjugateBtn.text = ""
821+
binding.conjugateBtn.setOnClickListener(null)
822+
binding.conjugateBtn.visibility = View.GONE
823+
binding.separator2.visibility = View.GONE
824+
binding.separator3.visibility = View.GONE
825+
} else {
826+
val suggestion2 = suggestions.getOrNull(1) ?: ""
827+
binding.conjugateBtn.visibility = View.VISIBLE
828+
binding.conjugateBtn.text = suggestion2
829+
binding.conjugateBtn.setOnClickListener(createSuggestionClickListener(suggestion2))
830+
binding.separator2.visibility = View.VISIBLE
831+
binding.separator3.visibility = View.VISIBLE
832+
}
807833

808834
val suggestion3 = suggestions.getOrNull(2) ?: ""
809835
binding.pluralBtn.text = suggestion3

app/src/keyboards/java/be/scri/services/EnglishKeyboardIME.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ class EnglishKeyboardIME : GeneralKeyboardIME("English") {
3232
override var inputTypeClass: Int = InputType.TYPE_CLASS_TEXT
3333
override var enterKeyType: Int = IME_ACTION_NONE
3434
override var switchToLetters: Boolean = false
35-
override var hasTextBeforeCursor: Boolean = false
3635

3736
private val keyHandler by lazy { KeyHandler(this) }
3837

app/src/keyboards/java/be/scri/services/GeneralKeyboardIME.kt

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,8 @@ abstract class GeneralKeyboardIME(
123123
private val shiftPermToggleSpeed: Int = DEFAULT_SHIFT_PERM_TOGGLE_SPEED
124124

125125
private lateinit var dbManagers: DatabaseManagers
126-
private lateinit var suggestionHandler: SuggestionHandler
127-
private lateinit var autocompletionHandler: AutocompletionHandler
126+
internal lateinit var suggestionHandler: SuggestionHandler
127+
internal lateinit var autocompletionHandler: AutocompletionHandler
128128
private lateinit var autocompletionManager: AutocompletionDataManager
129129
private var dataContract: DataContract? = null
130130

@@ -146,6 +146,7 @@ abstract class GeneralKeyboardIME(
146146
var wordSuggestions: List<String>? = null
147147
var checkIfPluralWord: Boolean = false
148148
private var currentEnterKeyType: Int? = null
149+
private var isNumericKeyboardActive: Boolean = false
149150

150151
internal var currentState: ScribeState = ScribeState.IDLE
151152
internal var invalidCommandSource: ScribeState = ScribeState.IDLE
@@ -178,6 +179,22 @@ abstract class GeneralKeyboardIME(
178179
internal const val MAX_TEXT_LENGTH = 1000
179180
const val COMMIT_TEXT_CURSOR_POSITION = 1
180181
internal const val CUSTOM_CURSOR = "" // special tall cursor character
182+
183+
internal fun shouldUseNumericKeyboard(inputType: Int): Boolean =
184+
when (inputType and TYPE_MASK_CLASS) {
185+
TYPE_CLASS_NUMBER, TYPE_CLASS_DATETIME, TYPE_CLASS_PHONE -> true
186+
else -> false
187+
}
188+
189+
internal fun getKeyboardLayoutXMLForInputType(
190+
inputType: Int,
191+
letterKeyboardLayoutXML: Int,
192+
): Int =
193+
if (shouldUseNumericKeyboard(inputType)) {
194+
R.xml.keys_numeric
195+
} else {
196+
letterKeyboardLayoutXML
197+
}
181198
}
182199

183200
// MARK: Lifecycle Methods
@@ -286,25 +303,16 @@ abstract class GeneralKeyboardIME(
286303
// This setter triggers the logic in the property override if not shadowed.
287304
hasTextBeforeCursor = currentInputConnection?.getTextBeforeCursor(1, 0)?.isNotEmpty() == true
288305

289-
val keyboardXml =
290-
when (inputTypeClass) {
291-
TYPE_CLASS_NUMBER, TYPE_CLASS_DATETIME, TYPE_CLASS_PHONE -> {
292-
keyboardMode = keyboardSymbols
293-
R.xml.keys_symbols
294-
}
295-
296-
else -> {
297-
keyboardMode = keyboardLetters
298-
getKeyboardLayoutXML()
299-
}
300-
}
306+
isNumericKeyboardActive = shouldUseNumericKeyboard(attribute.inputType)
307+
keyboardMode = if (isNumericKeyboardActive) keyboardSymbols else keyboardLetters
308+
val keyboardXml = getKeyboardLayoutXMLForInputType(attribute.inputType, getKeyboardLayoutXML())
301309

302310
loadLanguageData()
303311

304312
keyboard = KeyboardBase(this, keyboardXml, enterKeyType)
305313
keyboardView?.setKeyboard(keyboard!!)
306314

307-
if (keyboardXml == R.xml.keys_symbols) {
315+
if (this::uiManager.isInitialized && keyboardXml == R.xml.keys_symbols) {
308316
uiManager.setupCurrencySymbol(language)
309317
}
310318
}
@@ -334,7 +342,7 @@ abstract class GeneralKeyboardIME(
334342
banner.visibility =
335343
if (hasData) View.GONE else View.VISIBLE
336344
binding.commandOptionsBar.visibility =
337-
if (hasData) View.VISIBLE else View.GONE
345+
if (hasData && !isNumericKeyboardActive) View.VISIBLE else View.GONE
338346
val isDarkMode = getIsDarkModeOrNot(applicationContext)
339347
val bannerColor = if (isDarkMode) R.color.dark_tutorial_button_color else R.color.light_tutorial_button_color
340348
val bannerTextColor = if (isDarkMode) R.color.dark_button_outline_color else R.color.light_text_color
@@ -727,6 +735,22 @@ abstract class GeneralKeyboardIME(
727735

728736
override fun getCurrentEnterKeyType(): Int = enterKeyType
729737

738+
override fun isNumericKeyboardActive(): Boolean = isNumericKeyboardActive
739+
740+
override fun getCurrentKeyboardLayoutXML(): Int =
741+
when (keyboardMode) {
742+
keyboardSymbols -> getPrimarySymbolKeyboardLayoutXML()
743+
keyboardSymbolShift -> R.xml.keys_symbols_shift
744+
else -> getKeyboardLayoutXML()
745+
}
746+
747+
private fun getPrimarySymbolKeyboardLayoutXML(): Int =
748+
if (isNumericKeyboardActive) {
749+
R.xml.keys_numeric
750+
} else {
751+
R.xml.keys_symbols
752+
}
753+
730754
override fun onKeyboardActionListener(): KeyboardView.OnKeyboardActionListener = this
731755

732756
override fun processLinguisticSuggestions(word: String) {
@@ -1171,7 +1195,7 @@ abstract class GeneralKeyboardIME(
11711195
R.xml.keys_symbols_shift
11721196
} else {
11731197
this.keyboardMode = keyboardSymbols
1174-
R.xml.keys_symbols
1198+
getPrimarySymbolKeyboardLayoutXML()
11751199
}
11761200
keyboard = KeyboardBase(this, keyboardXml, enterKeyType)
11771201
keyboardView!!.setKeyboard(keyboard!!)
@@ -1196,7 +1220,7 @@ abstract class GeneralKeyboardIME(
11961220
val keyboardXml =
11971221
if (keyboardMode == keyboardLetters) {
11981222
this.keyboardMode = keyboardSymbols
1199-
R.xml.keys_symbols
1223+
getPrimarySymbolKeyboardLayoutXML()
12001224
} else {
12011225
this.keyboardMode = keyboardLetters
12021226
getKeyboardLayoutXML()

app/src/keyboards/java/be/scri/services/ItalianKeyboardIME.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ class ItalianKeyboardIME : GeneralKeyboardIME("Italian") {
3232
override var inputTypeClass: Int = InputType.TYPE_CLASS_TEXT
3333
override var enterKeyType: Int = IME_ACTION_NONE
3434
override var switchToLetters: Boolean = false
35-
override var hasTextBeforeCursor: Boolean = false
3635

3736
private val keyHandler by lazy { KeyHandler(this) }
3837

app/src/keyboards/java/be/scri/services/RussianKeyboardIME.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ class RussianKeyboardIME : GeneralKeyboardIME("Russian") {
3232
override var inputTypeClass: Int = InputType.TYPE_CLASS_TEXT
3333
override var enterKeyType: Int = IME_ACTION_NONE
3434
override var switchToLetters: Boolean = false
35-
override var hasTextBeforeCursor: Boolean = false
3635

3736
private val keyHandler by lazy { KeyHandler(this) }
3837

app/src/keyboards/java/be/scri/services/SwedishKeyboardIME.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ class SwedishKeyboardIME : GeneralKeyboardIME("Swedish") {
4242
override var inputTypeClass: Int = InputType.TYPE_CLASS_TEXT
4343
override var enterKeyType: Int = IME_ACTION_NONE
4444
override var switchToLetters: Boolean = false
45-
override var hasTextBeforeCursor: Boolean = false
4645

4746
private val keyHandler by lazy { KeyHandler(this) }
4847

app/src/main/java/be/scri/App.kt

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import be.scri.ui.screens.ThirdPartyScreen
4343
import be.scri.ui.screens.WikimediaScreen
4444
import be.scri.ui.screens.about.AboutScreen
4545
import be.scri.ui.screens.download.CheckUpdateActions
46+
import be.scri.ui.screens.download.ConjugateDataDownloadViewModel
4647
import be.scri.ui.screens.download.ConjugateDownloadDataScreen
4748
import be.scri.ui.screens.download.DataDownloadViewModel
4849
import be.scri.ui.screens.download.DownloadActions
@@ -85,6 +86,7 @@ fun ScribeApp(
8586
isIncreaseTextSize: Boolean,
8687
modifier: Modifier = Modifier,
8788
downloadViewModel: DataDownloadViewModel = viewModel(),
89+
conjugateDownloadViewModel: ConjugateDataDownloadViewModel = viewModel(),
8890
) {
8991
val coroutineScope = rememberCoroutineScope()
9092
val navBackStackEntry by navController.currentBackStackEntryAsState()
@@ -107,6 +109,26 @@ fun ScribeApp(
107109
cancelCheckForNewData = downloadViewModel::cancelCheckForNewData,
108110
)
109111

112+
// Conjugate-specific download actions
113+
val conjugateDownloadStates = conjugateDownloadViewModel.downloadStates
114+
val onConjugateDownloadAction = conjugateDownloadViewModel::handleDownloadAction
115+
val onConjugateDownloadAll = conjugateDownloadViewModel::handleDownloadAllLanguages
116+
val initializeConjugateStates = conjugateDownloadViewModel::initializeStates
117+
val conjugateDownloadActions =
118+
DownloadActions(
119+
downloadStates = conjugateDownloadStates,
120+
onDownloadAction = onConjugateDownloadAction,
121+
onDownloadAll = onConjugateDownloadAll,
122+
initializeStates = initializeConjugateStates,
123+
)
124+
val conjugateCheckUpdateState by conjugateDownloadViewModel.checkUpdateState.collectAsState()
125+
val conjugateCheckUpdateActions =
126+
CheckUpdateActions(
127+
checkUpdateState = conjugateCheckUpdateState,
128+
checkForNewData = conjugateDownloadViewModel::checkForNewData,
129+
cancelCheckForNewData = conjugateDownloadViewModel::cancelCheckForNewData,
130+
)
131+
110132
val screens = remember(context) { BottomBarScreen.getScreens() }
111133
ScribeTheme(
112134
useDarkTheme = isDarkTheme,
@@ -265,8 +287,8 @@ fun ScribeApp(
265287
navController.popBackStack()
266288
},
267289
isDarkTheme = isDarkTheme,
268-
downloadActions = downloadActions,
269-
checkUpdateActions = checkUpdateActions,
290+
downloadActions = conjugateDownloadActions,
291+
checkUpdateActions = conjugateCheckUpdateActions,
270292
modifier = Modifier.padding(innerPadding),
271293
)
272294
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// SPDX-License-Identifier: GPL-3.0-or-later
2+
3+
package be.scri.data.remote
4+
5+
import android.content.ContentValues
6+
import android.content.Context
7+
import android.database.sqlite.SQLiteDatabase
8+
import android.database.sqlite.SQLiteException
9+
import android.database.sqlite.SQLiteOpenHelper
10+
import android.util.Log
11+
import be.scri.data.model.DataResponse
12+
13+
/**
14+
* Helper class for managing dynamic SQLite databases for conjugate data (verbs only).
15+
* It creates only the verbs table and inserts verb data according to the provided DataResponse.
16+
*/
17+
class ConjugateDynamicDbHelper(
18+
context: Context,
19+
language: String,
20+
) : SQLiteOpenHelper(context, "${language.uppercase()}ConjugateData.sqlite", null, 1) {
21+
override fun onCreate(db: SQLiteDatabase) {
22+
// Tables are created dynamically via syncConjugateDatabase from API contract.
23+
}
24+
25+
override fun onUpgrade(
26+
db: SQLiteDatabase,
27+
old: Int,
28+
new: Int,
29+
) {
30+
// Dynamic schema updates are handled via syncConjugateDatabase.
31+
}
32+
33+
/**
34+
* Synchronizes the conjugate database schema and data based on the provided DataResponse.
35+
* Only creates the verbs table and inserts verb data, ignoring other data types.
36+
* @param response The data response containing the contract and data to be inserted.
37+
*/
38+
fun syncConjugateDatabase(response: DataResponse) {
39+
val db = writableDatabase
40+
try {
41+
db.beginTransaction()
42+
43+
// Check if verbs table exists in the contract
44+
val verbsColumns = response.contract.fields["verbs"]
45+
if (verbsColumns != null) {
46+
// Create verbs table
47+
val colDefinition = verbsColumns.keys.joinToString(", ") { "$it TEXT" }
48+
db.execSQL("DROP TABLE IF EXISTS verbs")
49+
db.execSQL(
50+
"CREATE TABLE verbs " +
51+
"(id INTEGER PRIMARY KEY AUTOINCREMENT, $colDefinition)",
52+
)
53+
54+
// Insert verbs data
55+
val verbsData = response.data["verbs"]
56+
if (verbsData != null) {
57+
val cv = ContentValues()
58+
verbsData.forEach { row ->
59+
cv.clear()
60+
row.forEach { (key, value) ->
61+
cv.put(key, value?.toString() ?: "")
62+
}
63+
db.insert("verbs", null, cv)
64+
}
65+
Log.i("CONJUGATE_DB", "Successfully synced ${verbsData.size} verb records for ${response.language}")
66+
} else {
67+
Log.w("CONJUGATE_DB", "No verbs data found in response for ${response.language}")
68+
}
69+
} else {
70+
Log.e("CONJUGATE_DB", "No verbs table found in contract for ${response.language}")
71+
}
72+
73+
db.setTransactionSuccessful()
74+
} catch (e: SQLiteException) {
75+
Log.e("CONJUGATE_DB", "Error during conjugate database sync: ${e.message}")
76+
throw e
77+
} finally {
78+
db.endTransaction()
79+
db.close()
80+
}
81+
}
82+
}

0 commit comments

Comments
 (0)