Adding settings. Fixes #1

This commit is contained in:
Dessalines 2023-03-14 15:52:44 -04:00
parent 17b25823c8
commit 7aade688e2
18 changed files with 1099 additions and 148 deletions

1
.gitignore vendored
View File

@ -11,6 +11,7 @@
.cxx
local.properties
app/release
app/schemas
.project
.settings
.classpath

View File

@ -1,6 +1,7 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'com.google.devtools.ksp'
}
android {
@ -9,7 +10,7 @@ android {
defaultConfig {
applicationId "com.dessalines.thumbkey"
minSdk 21
targetSdk 31
targetSdk 33
versionCode 1
versionName "1.0.0"
@ -17,6 +18,22 @@ android {
vectorDrawables {
useSupportLibrary true
}
ksp { arg('room.schemaLocation', "$projectDir/schemas") }
}
if(project.hasProperty("RELEASE_STORE_FILE")) {
signingConfigs {
release {
storeFile file(RELEASE_STORE_FILE)
storePassword RELEASE_STORE_PASSWORD
keyAlias RELEASE_KEY_ALIAS
keyPassword RELEASE_KEY_PASSWORD
// Optional, specify signing versions used
v1SigningEnabled true
v2SigningEnabled true
}
}
}
buildTypes {
@ -24,14 +41,18 @@ android {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
if(project.hasProperty("RELEASE_STORE_FILE")) {
signingConfig signingConfigs.release
}
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = '1.8'
jvmTarget = '17'
freeCompilerArgs = ['-Xjvm-default=all-compatibility', '-opt-in=kotlin.RequiresOptIn']
}
buildFeatures {
compose true
@ -43,6 +64,32 @@ android {
}
dependencies {
implementation "androidx.navigation:navigation-compose:2.5.3"
implementation 'com.github.alorma:compose-settings-ui-m3:0.15.0'
implementation 'androidx.navigation:navigation-runtime-ktx:2.5.3'
// To use Kotlin annotation processing tool
ksp "androidx.room:room-compiler:2.5.0"
implementation "androidx.room:room-runtime:2.5.0"
annotationProcessor "androidx.room:room-compiler:2.5.0"
// optional - Kotlin Extensions and Coroutines support for Room
implementation("androidx.room:room-ktx:2.5.0")
// optional - Test helpers
testImplementation "androidx.room:room-testing:2.5.0"
// optional - Paging 3 Integration
implementation "androidx.room:room-paging:2.5.0"
// LiveData
implementation "androidx.compose.runtime:runtime-livedata:1.3.3"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.5.1"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.8.10"
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation "androidx.compose.ui:ui:1.3.3"
@ -51,7 +98,6 @@ dependencies {
implementation "androidx.compose.material3:material3-window-size-class:1.0.1"
implementation "androidx.compose.ui:ui-tooling:1.3.3"
implementation "androidx.activity:activity-compose:1.6.1"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1'
implementation "com.louiscad.splitties:splitties-systemservices:3.0.0"
implementation "com.louiscad.splitties:splitties-views:3.0.0"
}

View File

@ -2,6 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:name=".ThumbkeyApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"

View File

@ -1,17 +1,28 @@
package com.dessalines.thumbkey
import android.annotation.SuppressLint
import android.content.Context
import androidx.compose.runtime.Composable
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.platform.AbstractComposeView
import com.dessalines.thumbkey.ui.components.KeyboardScreen
import com.dessalines.thumbkey.ui.theme.MainTheme
import androidx.lifecycle.LiveData
import com.dessalines.thumbkey.db.AppSettings
import com.dessalines.thumbkey.ui.components.keyboard.KeyboardScreen
import com.dessalines.thumbkey.ui.theme.ThumbkeyTheme
class ComposeKeyboardView(context: Context) : AbstractComposeView(context) {
@SuppressLint("ViewConstructor")
class ComposeKeyboardView(context: Context, private val liveSettings: LiveData<AppSettings>) :
AbstractComposeView
(context) {
@Composable
override fun Content() {
MainTheme {
KeyboardScreen()
val settings = liveSettings.observeAsState().value
ThumbkeyTheme(
settings = settings
) {
KeyboardScreen(settings)
}
}
}

View File

@ -15,7 +15,8 @@ class IMEService :
SavedStateRegistryOwner {
override fun onCreateInputView(): View {
val view = ComposeKeyboardView(this)
val liveSettings = (application as ThumbkeyApplication).appSettingsRepository.appSettings
val view = ComposeKeyboardView(this, liveSettings)
window?.window?.decorView?.let { decorView ->
ViewTreeLifecycleOwner.set(decorView, this)

View File

@ -1,83 +1,73 @@
package com.dessalines.thumbkey
import android.content.Intent
import android.app.Application
import android.os.Bundle
import android.provider.Settings
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp
import com.dessalines.thumbkey.ui.theme.MainTheme
import com.dessalines.thumbkey.utils.THUMBKEY_IME_NAME
import splitties.systemservices.inputMethodManager
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.dessalines.thumbkey.db.AppDB
import com.dessalines.thumbkey.db.AppSettingsRepository
import com.dessalines.thumbkey.db.AppSettingsViewModel
import com.dessalines.thumbkey.db.AppSettingsViewModelFactory
import com.dessalines.thumbkey.ui.components.settings.SettingsActivity
import com.dessalines.thumbkey.ui.components.settings.lookandfeel.LookAndFeelActivity
import com.dessalines.thumbkey.ui.components.setup.SetupActivity
import com.dessalines.thumbkey.ui.theme.ThumbkeyTheme
class ThumbkeyApplication : Application() {
private val database by lazy { AppDB.getDatabase(this) }
val appSettingsRepository by lazy { AppSettingsRepository(database.appSettingsDao()) }
}
class MainActivity : AppCompatActivity() {
private val appSettingsViewModel: AppSettingsViewModel by viewModels {
AppSettingsViewModelFactory((application as ThumbkeyApplication).appSettingsRepository)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MainTheme {
Surface(modifier = Modifier.fillMaxSize()) {
Column {
Options()
Spacer(modifier = Modifier.weight(1f))
val settings by appSettingsViewModel.appSettings.observeAsState()
ThumbkeyTheme(
settings = settings
) {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "settings" // TODO
) {
composable(
route = "setup"
) {
SetupActivity(navController = navController)
}
composable(route = "settings") {
SettingsActivity(navController = navController)
}
composable(route = "lookAndFeel") {
LookAndFeelActivity(
navController = navController,
appSettingsViewModel = appSettingsViewModel
)
}
composable(
route = "about"
) {
// AboutActivity(
// navController = navController
// )
}
}
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Options() {
Column(
Modifier
.padding(16.dp)
.fillMaxWidth()
) {
val ctx = LocalContext.current
val thumbkeyEnabled = inputMethodManager.enabledInputMethodList.any {
it.id == THUMBKEY_IME_NAME
}
val thumbkeySelected = Settings.Secure.getString(ctx.contentResolver, Settings.Secure.DEFAULT_INPUT_METHOD) == THUMBKEY_IME_NAME
Text(text = "Compose Keyboard")
val (text, setValue) = remember { mutableStateOf(TextFieldValue("Try here")) }
if (!thumbkeyEnabled) {
Spacer(modifier = Modifier.height(16.dp))
Button(modifier = Modifier.fillMaxWidth(), onClick = {
ctx.startActivity(Intent(Settings.ACTION_INPUT_METHOD_SETTINGS))
}) {
Text(text = "Enable Thumbkey")
}
}
if (!thumbkeySelected) {
Spacer(modifier = Modifier.height(16.dp))
Button(modifier = Modifier.fillMaxWidth(), onClick = {
inputMethodManager.showInputMethodPicker()
}) {
Text(text = "Select Thumbkey")
}
}
// All settings here
Spacer(modifier = Modifier.height(16.dp))
TextField(value = text, onValueChange = setValue, modifier = Modifier.fillMaxWidth())
}
}

View File

@ -1,32 +0,0 @@
package com.dessalines.thumbkey
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Button
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
@Composable
fun MockKeyboard() {
Column(
Modifier
.fillMaxWidth()
.height(200.dp)
.background(Color.Gray),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(color = Color.Black, text = "This should resemble a keyboard")
Button(modifier = Modifier.width(250.dp), onClick = { }) {
Text(text = "A Button")
}
Text(color = Color.Black, text = "Example for Linear Progress Indicator")
Spacer(modifier = Modifier.height(16.dp))
LinearProgressIndicator()
}
}

View File

@ -0,0 +1,180 @@
package com.dessalines.thumbkey.db
import android.content.ContentValues
import android.content.Context
import android.database.sqlite.SQLiteDatabase.CONFLICT_IGNORE
import androidx.annotation.WorkerThread
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import androidx.room.*
import androidx.sqlite.db.SupportSQLiteDatabase
import kotlinx.coroutines.launch
import java.util.concurrent.Executors
const val DEFAULT_KEY_SIZE = 64
const val DEFAULT_ANIMATION_SPEED = 350
const val DEFAULT_ANIMATION_HELPER_SPEED = 500
const val DEFAULT_POSITION = 0
const val DEFAULT_AUTO_CAPITALIZE = 1
const val DEFAULT_KEYBOARD_LAYOUT = 0
const val DEFAULT_THEME = 0
const val DEFAULT_THEME_COLOR = 0
const val UPDATE_APP_CHANGELOG_UNVIEWED = "UPDATE AppSettings SET viewed_changelog = 0"
@Entity
data class AppSettings(
@PrimaryKey(autoGenerate = true) val id: Int,
@ColumnInfo(
name = "key_size",
defaultValue = DEFAULT_KEY_SIZE.toString()
)
val keySize: Int,
@ColumnInfo(
name = "animation_speed",
defaultValue = DEFAULT_ANIMATION_SPEED.toString()
)
val animationSpeed: Int,
@ColumnInfo(
name = "animation_helper_speed",
defaultValue = DEFAULT_ANIMATION_HELPER_SPEED.toString()
)
val animationHelperSpeed: Int,
@ColumnInfo(
name = "position",
defaultValue = DEFAULT_POSITION.toString()
)
val position: Int,
@ColumnInfo(
name = "auto_capitalize",
defaultValue = DEFAULT_AUTO_CAPITALIZE.toString()
)
val autoCapitalize: Int,
@ColumnInfo(
name = "keyboard_layout",
defaultValue = DEFAULT_KEYBOARD_LAYOUT.toString()
)
val keyboardLayout: Int,
@ColumnInfo(
name = "theme",
defaultValue = DEFAULT_THEME.toString()
)
val theme: Int,
@ColumnInfo(
name = "theme_color",
defaultValue = DEFAULT_THEME_COLOR.toString()
)
val themeColor: Int,
@ColumnInfo(
name = "viewed_changelog",
defaultValue = "0"
)
val viewedChangelog: Int
)
@Dao
interface AppSettingsDao {
@Query("SELECT * FROM AppSettings limit 1")
fun getSettings(): LiveData<AppSettings>
@Update
suspend fun updateAppSettings(appSettings: AppSettings)
@Query("UPDATE AppSettings set viewed_changelog = 1")
suspend fun markChangelogViewed()
}
// Declares the DAO as a private property in the constructor. Pass in the DAO
// instead of the whole database, because you only need access to the DAO
class AppSettingsRepository(private val appSettingsDao: AppSettingsDao) {
// Room executes all queries on a separate thread.
// Observed Flow will notify the observer when the data has changed.
val appSettings = appSettingsDao.getSettings()
@WorkerThread
suspend fun update(appSettings: AppSettings) {
appSettingsDao.updateAppSettings(appSettings)
}
@WorkerThread
suspend fun markChangelogViewed() {
appSettingsDao.markChangelogViewed()
}
}
@Database(
version = 1,
entities = [AppSettings::class],
exportSchema = true
)
abstract class AppDB : RoomDatabase() {
abstract fun appSettingsDao(): AppSettingsDao
companion object {
@Volatile
private var INSTANCE: AppDB? = null
fun getDatabase(
context: Context
): AppDB {
// if the INSTANCE is not null, then return it,
// if it is, then create the database
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AppDB::class.java,
"jerboa"
)
.allowMainThreadQueries()
// .addMigrations(
// MIGRATION_1_2,
// )
// Necessary because it can't insert data on creation
.addCallback(object : Callback() {
override fun onOpen(db: SupportSQLiteDatabase) {
super.onCreate(db)
Executors.newSingleThreadExecutor().execute {
db.insert(
"AppSettings",
CONFLICT_IGNORE, // Ensures it won't overwrite the existing data
ContentValues(2).apply {
put("id", 1)
}
)
}
}
}).build()
INSTANCE = instance
// return instance
instance
}
}
}
}
class AppSettingsViewModel(private val repository: AppSettingsRepository) : ViewModel() {
val appSettings = repository.appSettings
fun update(appSettings: AppSettings) = viewModelScope.launch {
repository.update(appSettings)
}
fun markChangelogViewed() = viewModelScope.launch {
repository.markChangelogViewed()
}
}
class AppSettingsViewModelFactory(private val repository: AppSettingsRepository) :
ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(AppSettingsViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return AppSettingsViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}

View File

@ -1,5 +1,4 @@
package com.dessalines.thumbkey.ui.components
package com.dessalines.thumbkey.ui.components.keyboard
import android.util.Log
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExitTransition
@ -52,9 +51,14 @@ fun KeyboardKey(
key: KeyItemC,
mode: KeyboardMode,
lastAction: MutableState<KeyAction?>,
animationHelperSpeed: Int,
animationSpeed: Int,
autoCapitalize: Boolean,
keySize: Int,
onToggleShiftMode: (enable: Boolean) -> Unit,
onToggleNumericMode: (enable: Boolean) -> Unit
) {
val id = key.toString() + animationHelperSpeed + animationSpeed + keySize + mode
val ctx = LocalContext.current
val ime = ctx as IMEService
val scope = rememberCoroutineScope()
@ -74,18 +78,17 @@ fun KeyboardKey(
MaterialTheme.colorScheme.inversePrimary
}
val keySize = 80.dp
val keyPadding = 2.dp
val keySizeDp = keySize.dp
val animationSpeed = 100
val keyPadding = 4.dp
val keyboardKeyModifier =
Modifier
.height(keySize)
.width(keySize * key.widthMultiplier)
.height(keySizeDp)
.width(keySizeDp * key.widthMultiplier)
.padding(.5.dp)
.background(color = backgroundColor)
.pointerInput(key1 = key) {
.pointerInput(key1 = id) {
detectTapGestures(
onPress = {
pressed.value = true
@ -108,15 +111,16 @@ fun KeyboardKey(
action = action,
ime = ime,
mode = mode,
autoCapitalize = autoCapitalize,
onToggleShiftMode = onToggleShiftMode,
onToggleNumericMode = onToggleNumericMode
)
doneKeyAction(scope, action, pressed, releasedKey)
doneKeyAction(scope, action, pressed, releasedKey, animationHelperSpeed)
}
)
}
// The key1 is necessary, otherwise new swipes wont work
.pointerInput(key1 = key) {
.pointerInput(key1 = id) {
detectDragGestures(
onDragStart = {
pressed.value = true
@ -128,7 +132,7 @@ fun KeyboardKey(
offsetY += y
},
onDragEnd = {
val leeway = keySize.value
val leeway = keySizeDp.value
val swipeDirection = swipeDirection(offsetX, offsetY, leeway)
Log.d(
@ -143,16 +147,17 @@ fun KeyboardKey(
performKeyAction(
action = action,
ime = ime,
autoCapitalize = autoCapitalize,
mode = mode,
onToggleShiftMode = onToggleShiftMode,
onToggleNumericMode = onToggleNumericMode,
mode = mode
onToggleNumericMode = onToggleNumericMode
)
// Reset the drags
offsetX = 0f
offsetY = 0f
doneKeyAction(scope, action, pressed, releasedKey)
doneKeyAction(scope, action, pressed, releasedKey, animationHelperSpeed)
}
)
}
@ -167,7 +172,7 @@ fun KeyboardKey(
modifier = Modifier.fillMaxSize().padding(horizontal = keyPadding)
) {
key.swipes?.get(SwipeDirection.TOP_LEFT)?.let {
KeyText(it, keySize)
KeyText(it, keySizeDp)
}
}
Box(
@ -175,7 +180,7 @@ fun KeyboardKey(
modifier = Modifier.fillMaxSize().padding(horizontal = keyPadding)
) {
key.swipes?.get(SwipeDirection.TOP)?.let {
KeyText(it, keySize)
KeyText(it, keySizeDp)
}
}
Box(
@ -183,7 +188,7 @@ fun KeyboardKey(
modifier = Modifier.fillMaxSize().padding(horizontal = keyPadding)
) {
key.swipes?.get(SwipeDirection.TOP_RIGHT)?.let {
KeyText(it, keySize)
KeyText(it, keySizeDp)
}
}
Box(
@ -191,14 +196,14 @@ fun KeyboardKey(
modifier = Modifier.fillMaxSize().padding(horizontal = keyPadding)
) {
key.swipes?.get(SwipeDirection.LEFT)?.let {
KeyText(it, keySize)
KeyText(it, keySizeDp)
}
}
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize().padding(horizontal = keyPadding)
) {
KeyText(key.center, keySize)
KeyText(key.center, keySizeDp)
}
Box(
@ -206,7 +211,7 @@ fun KeyboardKey(
modifier = Modifier.fillMaxSize().padding(horizontal = keyPadding)
) {
key.swipes?.get(SwipeDirection.RIGHT)?.let {
KeyText(it, keySize)
KeyText(it, keySizeDp)
}
}
Box(
@ -214,7 +219,7 @@ fun KeyboardKey(
modifier = Modifier.fillMaxSize().padding(horizontal = keyPadding)
) {
key.swipes?.get(SwipeDirection.BOTTOM_LEFT)?.let {
KeyText(it, keySize)
KeyText(it, keySizeDp)
}
}
Box(
@ -222,7 +227,7 @@ fun KeyboardKey(
modifier = Modifier.fillMaxSize().padding(horizontal = keyPadding)
) {
key.swipes?.get(SwipeDirection.BOTTOM)?.let {
KeyText(it, keySize)
KeyText(it, keySizeDp)
}
}
Box(
@ -230,7 +235,7 @@ fun KeyboardKey(
modifier = Modifier.fillMaxSize().padding(horizontal = keyPadding)
) {
key.swipes?.get(SwipeDirection.BOTTOM_RIGHT)?.let {
KeyText(it, keySize)
KeyText(it, keySizeDp)
}
}
// The popup overlay
@ -249,7 +254,7 @@ fun KeyboardKey(
) {
val fontSize = fontSizeVariantToFontSize(
fontSizeVariant = FontSizeVariant.LARGE,
keySize = keySize
keySize = keySizeDp
)
releasedKey.value?.let { text ->
Text(

View File

@ -1,4 +1,4 @@
package com.dessalines.thumbkey.ui.components
package com.dessalines.thumbkey.ui.components.keyboard
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
@ -8,22 +8,43 @@ 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 com.dessalines.thumbkey.keyboards.THUMBKEY_V4_KEYBOARD_MODES
import com.dessalines.thumbkey.db.AppSettings
import com.dessalines.thumbkey.db.DEFAULT_ANIMATION_HELPER_SPEED
import com.dessalines.thumbkey.db.DEFAULT_ANIMATION_SPEED
import com.dessalines.thumbkey.db.DEFAULT_AUTO_CAPITALIZE
import com.dessalines.thumbkey.db.DEFAULT_KEYBOARD_LAYOUT
import com.dessalines.thumbkey.db.DEFAULT_KEY_SIZE
import com.dessalines.thumbkey.db.DEFAULT_POSITION
import com.dessalines.thumbkey.utils.KeyAction
import com.dessalines.thumbkey.utils.KeyboardLayout
import com.dessalines.thumbkey.utils.KeyboardMode
import com.dessalines.thumbkey.utils.KeyboardPosition
import com.dessalines.thumbkey.utils.keyboardLayoutToModes
import com.dessalines.thumbkey.utils.keyboardPositionToAlignment
@Composable
fun KeyboardScreen() {
fun KeyboardScreen(settings: AppSettings?) {
var mode by remember {
mutableStateOf(KeyboardMode.SHIFTED)
}
val lastAction = remember { mutableStateOf<KeyAction?>(null) }
val keyboard = THUMBKEY_V4_KEYBOARD_MODES[mode]!!
val keyboard = keyboardLayoutToModes(KeyboardLayout.values()[settings?.keyboardLayout ?: DEFAULT_KEYBOARD_LAYOUT])[mode]!!
val alignment = keyboardPositionToAlignment(
KeyboardPosition.values()[
settings?.position
?: DEFAULT_POSITION
]
)
val autoCapitalize = (settings?.autoCapitalize ?: DEFAULT_AUTO_CAPITALIZE) == 1
// TODO Cache these keyboards somehow, because the shifted to non-shifted delay is slow
Box(
contentAlignment = Alignment.BottomEnd
contentAlignment = alignment
) {
Column(
modifier = Modifier
@ -37,6 +58,12 @@ fun KeyboardScreen() {
key = key,
lastAction = lastAction,
mode = mode,
keySize = settings?.keySize ?: DEFAULT_KEY_SIZE,
autoCapitalize = autoCapitalize,
animationSpeed = settings?.animationSpeed
?: DEFAULT_ANIMATION_SPEED,
animationHelperSpeed = settings?.animationHelperSpeed
?: DEFAULT_ANIMATION_HELPER_SPEED,
onToggleShiftMode = { enable ->
mode = if (enable) {
KeyboardMode.SHIFTED

View File

@ -0,0 +1,74 @@
package com.dessalines.thumbkey.ui.components.settings
import android.util.Log
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material.icons.outlined.InstallMobile
import androidx.compose.material.icons.outlined.Palette
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.navigation.NavController
import com.alorma.compose.settings.ui.SettingsMenuLink
import com.dessalines.thumbkey.utils.SimpleTopAppBar
import com.dessalines.thumbkey.utils.TAG
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SettingsActivity(
navController: NavController
) {
Log.d(TAG, "Got to settings activity")
val snackbarHostState = remember { SnackbarHostState() }
Scaffold(
snackbarHost = { SnackbarHost(snackbarHostState) },
topBar = {
SimpleTopAppBar(text = "Thumb-Key", navController = navController, showBack = false)
},
content = { padding ->
Column(modifier = Modifier.padding(padding)) {
SettingsMenuLink(
title = { Text("Setup") },
icon = {
Icon(
imageVector = Icons.Outlined.InstallMobile,
contentDescription = "TODO"
)
},
onClick = { navController.navigate("setup") }
)
SettingsMenuLink(
title = { Text("Look and feel") },
icon = {
Icon(
imageVector = Icons.Outlined.Palette,
contentDescription = "TODO"
)
},
onClick = { navController.navigate("lookAndFeel") }
)
SettingsMenuLink(
title = { Text("About") },
icon = {
Icon(
imageVector = Icons.Outlined.Info,
contentDescription = "TODO"
)
},
onClick = { navController.navigate("about") }
)
}
}
)
}

View File

@ -0,0 +1,337 @@
package com.dessalines.thumbkey.ui.components.settings.lookandfeel
import android.util.Log
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.*
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
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.Modifier
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.alorma.compose.settings.storage.base.SettingValueState
import com.alorma.compose.settings.storage.base.rememberBooleanSettingState
import com.alorma.compose.settings.storage.base.rememberFloatSettingState
import com.alorma.compose.settings.storage.base.rememberIntSettingState
import com.alorma.compose.settings.ui.SettingsCheckbox
import com.alorma.compose.settings.ui.SettingsList
import com.alorma.compose.settings.ui.SettingsSlider
import com.dessalines.thumbkey.db.AppSettings
import com.dessalines.thumbkey.db.AppSettingsViewModel
import com.dessalines.thumbkey.db.DEFAULT_ANIMATION_HELPER_SPEED
import com.dessalines.thumbkey.db.DEFAULT_ANIMATION_SPEED
import com.dessalines.thumbkey.db.DEFAULT_KEYBOARD_LAYOUT
import com.dessalines.thumbkey.db.DEFAULT_KEY_SIZE
import com.dessalines.thumbkey.utils.KeyboardLayout
import com.dessalines.thumbkey.utils.KeyboardPosition
import com.dessalines.thumbkey.utils.SimpleTopAppBar
import com.dessalines.thumbkey.utils.TAG
import com.dessalines.thumbkey.utils.ThemeColor
import com.dessalines.thumbkey.utils.ThemeMode
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LookAndFeelActivity(
navController: NavController,
appSettingsViewModel: AppSettingsViewModel
) {
Log.d(TAG, "Got to lookAndFeel activity")
val settings = appSettingsViewModel.appSettings.value
val keySizeState = rememberFloatSettingState((settings?.keySize ?: DEFAULT_KEY_SIZE).toFloat())
val animationSpeedState = rememberFloatSettingState(
(settings?.animationSpeed ?: DEFAULT_ANIMATION_SPEED).toFloat()
)
val animationHelperSpeedState = rememberFloatSettingState(
(
settings?.animationHelperSpeed
?: DEFAULT_ANIMATION_HELPER_SPEED
).toFloat()
)
val positionState = rememberIntSettingState(
settings?.position ?: com.dessalines.thumbkey
.db.DEFAULT_POSITION
)
val autoCapitalizeState = rememberBooleanSettingState(
(settings?.autoCapitalize?.equals(1))
?: true
)
val keyboardLayoutState = rememberIntSettingState(
settings?.keyboardLayout
?: DEFAULT_KEYBOARD_LAYOUT
)
val themeState = rememberIntSettingState(settings?.theme ?: 0)
val themeColorState = rememberIntSettingState(settings?.themeColor ?: 0)
val snackbarHostState = remember { SnackbarHostState() }
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState())
var text by remember { mutableStateOf("") }
Scaffold(
snackbarHost = { SnackbarHost(snackbarHostState) },
topBar = {
SimpleTopAppBar(text = "Look and feel", navController = navController, scrollBehavior = scrollBehavior)
},
content = { padding ->
Column(modifier = Modifier.padding(padding)) {
SettingsList(
state = keyboardLayoutState,
items = KeyboardLayout.values().map { it.name },
icon = {
Icon(
imageVector = Icons.Outlined.KeyboardAlt,
contentDescription = "TODO"
)
},
title = {
Text(text = "Layout")
},
action = {
updateAppSettings(
appSettingsViewModel,
keySizeState,
animationSpeedState,
animationHelperSpeedState,
positionState,
autoCapitalizeState,
keyboardLayoutState,
themeState,
themeColorState
)
}
)
SettingsList(
state = themeState,
items = ThemeMode.values().map { it.name },
icon = {
Icon(
imageVector = Icons.Outlined.Palette,
contentDescription = "TODO"
)
},
title = {
Text(text = "Theme")
},
action = {
updateAppSettings(
appSettingsViewModel,
keySizeState,
animationSpeedState,
animationHelperSpeedState,
positionState,
autoCapitalizeState,
keyboardLayoutState,
themeState,
themeColorState
)
}
)
SettingsList(
state = themeColorState,
items = ThemeColor.values().map { it.name },
icon = {
Icon(
imageVector = Icons.Outlined.Colorize,
contentDescription = "TODO"
)
},
title = {
Text(text = "Theme color")
},
action = {
updateAppSettings(
appSettingsViewModel,
keySizeState,
animationSpeedState,
animationHelperSpeedState,
positionState,
autoCapitalizeState,
keyboardLayoutState,
themeState,
themeColorState
)
}
)
SettingsCheckbox(
state = autoCapitalizeState,
icon = {
Icon(
imageVector = Icons.Outlined.Abc,
contentDescription = "TODO"
)
},
title = {
Text(text = "Auto-Capitalize")
},
onCheckedChange = {
updateAppSettings(
appSettingsViewModel,
keySizeState,
animationSpeedState,
animationHelperSpeedState,
positionState,
autoCapitalizeState,
keyboardLayoutState,
themeState,
themeColorState
)
}
)
SettingsList(
state = positionState,
items = KeyboardPosition.values().map { it.name },
icon = {
Icon(
imageVector = Icons.Outlined.LinearScale,
contentDescription = "TODO"
)
},
title = {
Text(text = "Position")
},
action = {
updateAppSettings(
appSettingsViewModel,
keySizeState,
animationSpeedState,
animationHelperSpeedState,
positionState,
autoCapitalizeState,
keyboardLayoutState,
themeState,
themeColorState
)
}
)
SettingsSlider(
valueRange = 10f..200f,
state = keySizeState,
icon = {
Icon(
imageVector = Icons.Outlined.FormatSize,
contentDescription = "TODO"
)
},
title = {
Text(text = "Key Size: ${keySizeState.value.toInt()}")
},
onValueChangeFinished = {
updateAppSettings(
appSettingsViewModel,
keySizeState,
animationSpeedState,
animationHelperSpeedState,
positionState,
autoCapitalizeState,
keyboardLayoutState,
themeState,
themeColorState
)
}
)
SettingsSlider(
valueRange = 0f..1000f,
state = animationSpeedState,
icon = {
Icon(
imageVector = Icons.Outlined.Animation,
contentDescription = "TODO"
)
},
title = {
Text(text = "Animation Speed: ${animationSpeedState.value.toInt()}")
},
onValueChangeFinished = {
updateAppSettings(
appSettingsViewModel,
keySizeState,
animationSpeedState,
animationHelperSpeedState,
positionState,
autoCapitalizeState,
keyboardLayoutState,
themeState,
themeColorState
)
}
)
SettingsSlider(
valueRange = 0f..1000f,
state = animationHelperSpeedState,
icon = {
Icon(
imageVector = Icons.Outlined.Visibility,
contentDescription = "TODO"
)
},
title = {
Text(text = "Animation Helper Speed: ${animationHelperSpeedState.value.toInt()}")
},
onValueChangeFinished = {
updateAppSettings(
appSettingsViewModel,
keySizeState,
animationSpeedState,
animationHelperSpeedState,
positionState,
autoCapitalizeState,
keyboardLayoutState,
themeState,
themeColorState
)
}
)
TextField(
modifier = Modifier.padding(16.dp).fillMaxWidth(),
value = text,
onValueChange = { text = it },
label = { Text("Test out Thumb-Key") }
)
}
}
)
}
private fun updateAppSettings(
appSettingsViewModel: AppSettingsViewModel,
keySizeState: SettingValueState<Float>,
animationSpeedState: SettingValueState<Float>,
animationHelperSpeedState: SettingValueState<Float>,
positionState: SettingValueState<Int>,
autoCapitalizeState: SettingValueState<Boolean>,
keyboardLayoutState: SettingValueState<Int>,
themeState: SettingValueState<Int>,
themeColorState: SettingValueState<Int>
) {
appSettingsViewModel.update(
AppSettings(
id = 1,
keySize = keySizeState.value.toInt(),
animationSpeed = animationSpeedState.value.toInt(),
animationHelperSpeed = animationHelperSpeedState.value.toInt(),
position = positionState.value,
autoCapitalize = autoCapitalizeState.value.compareTo(false),
keyboardLayout = keyboardLayoutState.value,
theme = themeState.value,
themeColor = themeColorState.value,
viewedChangelog = appSettingsViewModel.appSettings.value?.viewedChangelog ?: 0
)
)
}

View File

@ -0,0 +1,78 @@
package com.dessalines.thumbkey.ui.components.setup
import android.content.Intent
import android.provider.Settings
import android.util.Log
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.dessalines.thumbkey.utils.SimpleTopAppBar
import com.dessalines.thumbkey.utils.TAG
import com.dessalines.thumbkey.utils.THUMBKEY_IME_NAME
import splitties.systemservices.inputMethodManager
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SetupActivity(
navController: NavController
) {
Log.d(TAG, "Got to setup activity")
val snackbarHostState = remember { SnackbarHostState() }
val ctx = LocalContext.current
// TODO refactor these to be watchers
val thumbkeyEnabled = inputMethodManager.enabledInputMethodList.any {
it.id == THUMBKEY_IME_NAME
}
val thumbkeySelected = Settings.Secure.getString(
ctx.contentResolver,
Settings.Secure.DEFAULT_INPUT_METHOD
) == THUMBKEY_IME_NAME
Scaffold(
snackbarHost = { SnackbarHost(snackbarHostState) },
topBar = {
SimpleTopAppBar(text = "Setup Thumb-Key", navController = navController)
},
content = { padding ->
Column(
modifier = Modifier
.padding(padding)
.padding(horizontal = 16.dp)
) {
if (!thumbkeyEnabled) {
Spacer(modifier = Modifier.height(16.dp))
Button(modifier = Modifier.fillMaxWidth(), onClick = {
ctx.startActivity(Intent(Settings.ACTION_INPUT_METHOD_SETTINGS))
}) {
Text(text = "Enable Thumbkey")
}
}
if (!thumbkeySelected) {
Spacer(modifier = Modifier.height(16.dp))
Button(modifier = Modifier.fillMaxWidth(), onClick = {
inputMethodManager.showInputMethodPicker()
}) {
Text(text = "Select Thumbkey")
}
}
}
}
)
}

View File

@ -135,3 +135,134 @@ fun pink(): Pair<ColorScheme, ColorScheme> {
)
return Pair(light, dark)
}
fun green(): Pair<ColorScheme, ColorScheme> {
val md_theme_light_primary = Color(0xFF216C29)
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
val md_theme_light_primaryContainer = Color(0xFFA7F5A1)
val md_theme_light_onPrimaryContainer = Color(0xFF002204)
val md_theme_light_secondary = Color(0xFF52634F)
val md_theme_light_onSecondary = Color(0xFFFFFFFF)
val md_theme_light_secondaryContainer = Color(0xFFD5E8CF)
val md_theme_light_onSecondaryContainer = Color(0xFF101F10)
val md_theme_light_tertiary = Color(0xFF38656A)
val md_theme_light_onTertiary = Color(0xFFFFFFFF)
val md_theme_light_tertiaryContainer = Color(0xFFBCEBF1)
val md_theme_light_onTertiaryContainer = Color(0xFF002023)
val md_theme_light_error = Color(0xFFBA1A1A)
val md_theme_light_errorContainer = Color(0xFFFFDAD6)
val md_theme_light_onError = Color(0xFFFFFFFF)
val md_theme_light_onErrorContainer = Color(0xFF410002)
val md_theme_light_background = Color(0xFFFCFDF6)
val md_theme_light_onBackground = Color(0xFF1A1C19)
val md_theme_light_surface = Color(0xFFFCFDF6)
val md_theme_light_onSurface = Color(0xFF1A1C19)
val md_theme_light_surfaceVariant = Color(0xFFDEE5D8)
val md_theme_light_onSurfaceVariant = Color(0xFF424940)
val md_theme_light_outline = Color(0xFF72796F)
val md_theme_light_inverseOnSurface = Color(0xFFF0F1EB)
val md_theme_light_inverseSurface = Color(0xFF2F312D)
val md_theme_light_inversePrimary = Color(0xFF8BD987)
// val md_theme_light_shadow = Color(0xFF000000)
val md_theme_light_surfaceTint = Color(0xFF216C29)
val md_theme_light_outlineVariant = Color(0xFFC2C9BD)
val md_theme_light_scrim = Color(0xFF000000)
val md_theme_dark_primary = Color(0xFF8BD987)
val md_theme_dark_onPrimary = Color(0xFF00390B)
val md_theme_dark_primaryContainer = Color(0xFF005314)
val md_theme_dark_onPrimaryContainer = Color(0xFFA7F5A1)
val md_theme_dark_secondary = Color(0xFFB9CCB3)
val md_theme_dark_onSecondary = Color(0xFF253423)
val md_theme_dark_secondaryContainer = Color(0xFF3B4B38)
val md_theme_dark_onSecondaryContainer = Color(0xFFD5E8CF)
val md_theme_dark_tertiary = Color(0xFFA0CFD4)
val md_theme_dark_onTertiary = Color(0xFF00363B)
val md_theme_dark_tertiaryContainer = Color(0xFF1F4D52)
val md_theme_dark_onTertiaryContainer = Color(0xFFBCEBF1)
val md_theme_dark_error = Color(0xFFFFB4AB)
val md_theme_dark_errorContainer = Color(0xFF93000A)
val md_theme_dark_onError = Color(0xFF690005)
val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
val md_theme_dark_background = Color(0xFF1A1C19)
val md_theme_dark_onBackground = Color(0xFFE2E3DD)
val md_theme_dark_surface = Color(0xFF1A1C19)
val md_theme_dark_onSurface = Color(0xFFE2E3DD)
val md_theme_dark_surfaceVariant = Color(0xFF424940)
val md_theme_dark_onSurfaceVariant = Color(0xFFC2C9BD)
val md_theme_dark_outline = Color(0xFF8C9388)
val md_theme_dark_inverseOnSurface = Color(0xFF1A1C19)
val md_theme_dark_inverseSurface = Color(0xFFE2E3DD)
val md_theme_dark_inversePrimary = Color(0xFF216C29)
// val md_theme_dark_shadow = Color(0xFF000000)
val md_theme_dark_surfaceTint = Color(0xFF8BD987)
val md_theme_dark_outlineVariant = Color(0xFF424940)
val md_theme_dark_scrim = Color(0xFF000000)
// val seed = Color(0xFF78C475)
val light = lightColorScheme(
primary = md_theme_light_primary,
onPrimary = md_theme_light_onPrimary,
primaryContainer = md_theme_light_primaryContainer,
onPrimaryContainer = md_theme_light_onPrimaryContainer,
secondary = md_theme_light_secondary,
onSecondary = md_theme_light_onSecondary,
secondaryContainer = md_theme_light_secondaryContainer,
onSecondaryContainer = md_theme_light_onSecondaryContainer,
tertiary = md_theme_light_tertiary,
onTertiary = md_theme_light_onTertiary,
tertiaryContainer = md_theme_light_tertiaryContainer,
onTertiaryContainer = md_theme_light_onTertiaryContainer,
error = md_theme_light_error,
errorContainer = md_theme_light_errorContainer,
onError = md_theme_light_onError,
onErrorContainer = md_theme_light_onErrorContainer,
background = md_theme_light_background,
onBackground = md_theme_light_onBackground,
surface = md_theme_light_surface,
onSurface = md_theme_light_onSurface,
surfaceVariant = md_theme_light_surfaceVariant,
onSurfaceVariant = md_theme_light_onSurfaceVariant,
outline = md_theme_light_outline,
inverseOnSurface = md_theme_light_inverseOnSurface,
inverseSurface = md_theme_light_inverseSurface,
inversePrimary = md_theme_light_inversePrimary,
surfaceTint = md_theme_light_surfaceTint,
outlineVariant = md_theme_light_outlineVariant,
scrim = md_theme_light_scrim
)
val dark = darkColorScheme(
primary = md_theme_dark_primary,
onPrimary = md_theme_dark_onPrimary,
primaryContainer = md_theme_dark_primaryContainer,
onPrimaryContainer = md_theme_dark_onPrimaryContainer,
secondary = md_theme_dark_secondary,
onSecondary = md_theme_dark_onSecondary,
secondaryContainer = md_theme_dark_secondaryContainer,
onSecondaryContainer = md_theme_dark_onSecondaryContainer,
tertiary = md_theme_dark_tertiary,
onTertiary = md_theme_dark_onTertiary,
tertiaryContainer = md_theme_dark_tertiaryContainer,
onTertiaryContainer = md_theme_dark_onTertiaryContainer,
error = md_theme_dark_error,
errorContainer = md_theme_dark_errorContainer,
onError = md_theme_dark_onError,
onErrorContainer = md_theme_dark_onErrorContainer,
background = md_theme_dark_background,
onBackground = md_theme_dark_onBackground,
surface = md_theme_dark_surface,
onSurface = md_theme_dark_onSurface,
surfaceVariant = md_theme_dark_surfaceVariant,
onSurfaceVariant = md_theme_dark_onSurfaceVariant,
outline = md_theme_dark_outline,
inverseOnSurface = md_theme_dark_inverseOnSurface,
inverseSurface = md_theme_dark_inverseSurface,
inversePrimary = md_theme_dark_inversePrimary,
surfaceTint = md_theme_dark_surfaceTint,
outlineVariant = md_theme_dark_outlineVariant,
scrim = md_theme_dark_scrim
)
return Pair(light, dark)
}

View File

@ -7,11 +7,18 @@ import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import com.dessalines.thumbkey.db.AppSettings
import com.dessalines.thumbkey.utils.ThemeColor
import com.dessalines.thumbkey.utils.ThemeMode
@Composable
fun MainTheme(
fun ThumbkeyTheme(
settings: AppSettings?,
content: @Composable () -> Unit
) {
val themeMode = ThemeMode.values()[settings?.theme ?: 0]
val themeColor = ThemeColor.values()[settings?.themeColor ?: 0]
val ctx = LocalContext.current
val android12OrLater = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
@ -22,14 +29,26 @@ fun MainTheme(
pink()
}
val colorPair = when (themeColor) {
ThemeColor.Dynamic -> dynamicPair
ThemeColor.Green -> green()
ThemeColor.Pink -> pink()
}
val systemTheme = if (!isSystemInDarkTheme()) {
dynamicPair.first
colorPair.first
} else {
dynamicPair.second
colorPair.second
}
val colors = when (themeMode) {
ThemeMode.System -> systemTheme
ThemeMode.Light -> colorPair.first
ThemeMode.Dark -> colorPair.second
}
MaterialTheme(
colorScheme = systemTheme,
colorScheme = colors,
typography = Typography,
shapes = Shapes,
content = content

View File

@ -52,3 +52,26 @@ enum class ColorVariant {
enum class FontSizeVariant {
LARGE, SMALL
}
enum class ThemeMode {
System,
Light,
Dark
}
enum class ThemeColor {
Dynamic,
Green,
Pink
}
enum class KeyboardLayout {
ThumbKeyV4,
MessageEase
}
enum class KeyboardPosition {
Right,
Center,
Left
}

View File

@ -1,16 +1,28 @@
package com.dessalines.thumbkey.utils
import android.util.Log
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.ui.Alignment
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.ExperimentalUnitApi
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
import androidx.core.text.trimmedLength
import androidx.navigation.NavController
import com.dessalines.thumbkey.IMEService
import com.dessalines.thumbkey.keyboards.MESSAGEEASE_KEYBOARD_MODES
import com.dessalines.thumbkey.keyboards.THUMBKEY_V4_KEYBOARD_MODES
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@ -39,6 +51,21 @@ fun fontSizeVariantToFontSize(fontSizeVariant: FontSizeVariant, keySize: Dp): Te
return TextUnit(keySize.value / divFactor, TextUnitType.Sp)
}
fun keyboardLayoutToModes(layout: KeyboardLayout): Map<KeyboardMode, KeyboardC> {
return when (layout) {
KeyboardLayout.ThumbKeyV4 -> THUMBKEY_V4_KEYBOARD_MODES
KeyboardLayout.MessageEase -> MESSAGEEASE_KEYBOARD_MODES
}
}
fun keyboardPositionToAlignment(position: KeyboardPosition): Alignment {
return when (position) {
KeyboardPosition.Right -> Alignment.BottomEnd
KeyboardPosition.Center -> Alignment.BottomCenter
KeyboardPosition.Left -> Alignment.BottomStart
}
}
fun swipeDirection(x: Float, y: Float, leeway: Float): SwipeDirection? {
return if (x > leeway) {
if (y > leeway) {
@ -70,9 +97,10 @@ fun swipeDirection(x: Float, y: Float, leeway: Float): SwipeDirection? {
fun performKeyAction(
action: KeyAction,
ime: IMEService,
mode: KeyboardMode,
autoCapitalize: Boolean,
onToggleShiftMode: (enable: Boolean) -> Unit,
onToggleNumericMode: (enable: Boolean) -> Unit,
mode: KeyboardMode
onToggleNumericMode: (enable: Boolean) -> Unit
) {
when (action) {
is KeyAction.CommitText -> {
@ -83,7 +111,8 @@ fun performKeyAction(
text.length
)
if (mode !== KeyboardMode.NUMERIC) {
// TODO this broke
if (autoCapitalize && mode !== KeyboardMode.NUMERIC) {
autoCapitalize(ime, onToggleShiftMode)
}
}
@ -158,11 +187,12 @@ fun doneKeyAction(
scope: CoroutineScope,
action: KeyAction,
pressed: MutableState<Boolean>,
releasedKey: MutableState<String?>
releasedKey: MutableState<String?>,
animationHelperSpeed: Int
) {
pressed.value = false
scope.launch {
delay(350)
delay(animationHelperSpeed.toLong())
releasedKey.value = null
}
releasedKey.value = when (action) {
@ -174,3 +204,31 @@ fun doneKeyAction(
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SimpleTopAppBar(
text: String,
navController: NavController,
scrollBehavior: TopAppBarScrollBehavior? = null,
showBack: Boolean = true
) {
TopAppBar(
scrollBehavior = scrollBehavior,
title = {
Text(
text = text
)
},
navigationIcon = {
if (showBack) {
IconButton(onClick = { navController.popBackStack() }) {
Icon(
Icons.Outlined.ArrowBack,
contentDescription = "Back"
)
}
}
}
)
}

View File

@ -12,6 +12,7 @@ plugins {
id 'com.android.library' version '8.1.0-alpha08' apply false
id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
id 'org.jmailen.kotlinter' version "3.13.0" apply false
id 'com.google.devtools.ksp' version "1.8.10-1.0.9" apply false
}
subprojects {