mirror of
https://github.com/dessalines/thumb-key.git
synced 2024-09-11 08:55:43 +03:00
Adding settings. Fixes #1
This commit is contained in:
parent
17b25823c8
commit
7aade688e2
1
.gitignore
vendored
1
.gitignore
vendored
@ -11,6 +11,7 @@
|
||||
.cxx
|
||||
local.properties
|
||||
app/release
|
||||
app/schemas
|
||||
.project
|
||||
.settings
|
||||
.classpath
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
180
app/src/main/java/com/dessalines/thumbkey/db/AppDb.kt
Normal file
180
app/src/main/java/com/dessalines/thumbkey/db/AppDb.kt
Normal 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")
|
||||
}
|
||||
}
|
@ -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(
|
@ -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
|
@ -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") }
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user