Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 6 additions & 13 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile

plugins {
kotlin("kapt")
alias(libs.plugins.android.application)
alias(libs.plugins.compose.compiler)
alias(libs.plugins.kotlin)
alias(libs.plugins.ktlint)
alias(libs.plugins.hilt)
alias(libs.plugins.kover)
id("org.jetbrains.kotlin.plugin.serialization")
}
Expand All @@ -17,8 +15,8 @@ android {
compileSdk = 36

compileOptions {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
Comment on lines +18 to +19
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Java version has been downgraded from 21 to 17. While this increases compatibility, please ensure this change is intentional and necessary. Java 21 is the latest LTS version and offers significant performance and feature improvements. If the downgrade is required for specific compatibility reasons, those reasons should be documented in the PR description or commit message.

Copilot uses AI. Check for mistakes.
}

defaultConfig {
Expand Down Expand Up @@ -65,7 +63,7 @@ tasks {
getByPath("preBuild").dependsOn("ktlintFormat")
withType<KotlinJvmCompile>().configureEach {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_21)
jvmTarget.set(JvmTarget.JVM_17)
freeCompilerArgs.add("-opt-in=kotlin.RequiresOptIn")
}
}
Expand Down Expand Up @@ -95,10 +93,9 @@ dependencies {
debugImplementation(libs.androidx.compose.ui.tooling)
debugImplementation(libs.androidx.compose.ui.test.manifest)

// Hilt
implementation(libs.hilt)
implementation(libs.hilt.navigation.compose)
kapt(libs.hilt.android.compiler)
// Koin
implementation(libs.koin.android)
implementation(libs.koin.androidx.compose)

// System Services
implementation(libs.splitties.systemservices)
Expand All @@ -113,7 +110,3 @@ dependencies {
testImplementation(composeBom)
testImplementation(libs.androidx.compose.ui.test.junit4)
}

kapt {
correctErrorTypes = true
}
18 changes: 15 additions & 3 deletions app/src/main/java/com/rickyhu/hushkeyboard/HushApplication.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
package com.rickyhu.hushkeyboard

import android.app.Application
import dagger.hilt.android.HiltAndroidApp
import com.rickyhu.hushkeyboard.di.appModule
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.GlobalContext
import org.koin.core.context.startKoin
import org.koin.core.context.stopKoin

@HiltAndroidApp
class HushApplication : Application()
class HushApplication : Application() {
override fun onCreate() {
super.onCreate()
if (GlobalContext.getOrNull() != null) stopKoin()
startKoin {
androidContext(this@HushApplication)
modules(appModule)
}
}
}
2 changes: 0 additions & 2 deletions app/src/main/java/com/rickyhu/hushkeyboard/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import com.rickyhu.hushkeyboard.main.MainApp
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,32 @@ package com.rickyhu.hushkeyboard.data

import android.content.Context
import androidx.datastore.dataStore
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject

private val Context.dataStore by dataStore("app-settings.json", AppSettingsSerializer)

class SettingsRepository
@Inject
constructor(
@ApplicationContext context: Context,
) {
private val dataStore = context.dataStore
val settings = dataStore.data

suspend fun updateThemeOption(themeOption: ThemeOption) {
dataStore.updateData { it.copy(themeOption = themeOption) }
}

suspend fun updateWideNotationOption(wideNotationOption: WideNotationOption) {
dataStore.updateData { it.copy(wideNotationOption = wideNotationOption) }
}

suspend fun updateSmartDelete(smartDelete: Boolean) {
dataStore.updateData { it.copy(smartDelete = smartDelete) }
}

suspend fun updateAddSpaceBetweenNotation(addSpaceBetweenNotation: Boolean) {
dataStore.updateData { it.copy(addSpaceAfterNotation = addSpaceBetweenNotation) }
}

suspend fun updateVibrateOnTap(vibrateOnTap: Boolean) {
dataStore.updateData { it.copy(vibrateOnTap = vibrateOnTap) }
}
class SettingsRepository(
context: Context,
) {
private val dataStore = context.dataStore
val settings = dataStore.data

suspend fun updateThemeOption(themeOption: ThemeOption) {
dataStore.updateData { it.copy(themeOption = themeOption) }
}

suspend fun updateWideNotationOption(wideNotationOption: WideNotationOption) {
dataStore.updateData { it.copy(wideNotationOption = wideNotationOption) }
}

suspend fun updateSmartDelete(smartDelete: Boolean) {
dataStore.updateData { it.copy(smartDelete = smartDelete) }
}

suspend fun updateAddSpaceBetweenNotation(addSpaceBetweenNotation: Boolean) {
dataStore.updateData { it.copy(addSpaceAfterNotation = addSpaceBetweenNotation) }
}

suspend fun updateVibrateOnTap(vibrateOnTap: Boolean) {
dataStore.updateData { it.copy(vibrateOnTap = vibrateOnTap) }
}
}
17 changes: 17 additions & 0 deletions app/src/main/java/com/rickyhu/hushkeyboard/di/AppModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.rickyhu.hushkeyboard.di

import com.rickyhu.hushkeyboard.data.SettingsRepository
import com.rickyhu.hushkeyboard.keyboard.KeyboardViewModel
import com.rickyhu.hushkeyboard.main.MainViewModel
import com.rickyhu.hushkeyboard.settings.SettingsViewModel
import org.koin.android.ext.koin.androidContext
import org.koin.core.module.dsl.viewModel
import org.koin.dsl.module

val appModule =
module {
single { SettingsRepository(androidContext()) }
viewModel { MainViewModel(get()) }
viewModel { SettingsViewModel(get()) }
viewModel { KeyboardViewModel(get()) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Done
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedTextField
Expand All @@ -19,6 +17,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview
Expand Down Expand Up @@ -84,7 +83,7 @@ private fun SetupDoneDialog(onDone: () -> Unit) {
AlertDialog(
icon = {
Icon(
imageVector = Icons.Default.Done,
painter = painterResource(R.drawable.ic_check),
contentDescription = "Done",
)
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,26 @@ import com.rickyhu.hushkeyboard.data.WideNotationOption
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import javax.inject.Inject

class KeyboardViewModel
@Inject
constructor(
settingsRepository: SettingsRepository,
) : ViewModel() {
val keyboardState =
settingsRepository.settings
.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5000),
AppSettings(),
).map { settings ->
KeyboardState(
themeOption = settings.themeOption,
wideNotationOption = settings.wideNotationOption,
smartDelete = settings.smartDelete,
addSpaceAfterNotation = settings.addSpaceAfterNotation,
vibrateOnTap = settings.vibrateOnTap,
)
}
}
class KeyboardViewModel(
settingsRepository: SettingsRepository,
) : ViewModel() {
val keyboardState =
settingsRepository.settings
.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5000),
AppSettings(),
).map { settings ->
KeyboardState(
themeOption = settings.themeOption,
wideNotationOption = settings.wideNotationOption,
smartDelete = settings.smartDelete,
addSpaceAfterNotation = settings.addSpaceAfterNotation,
vibrateOnTap = settings.vibrateOnTap,
)
}
}

data class KeyboardState(
val themeOption: ThemeOption = ThemeOption.System,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.rickyhu.hushkeyboard.keyboard
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.rickyhu.hushkeyboard.data.SettingsRepository

class KeyboardViewModelFactory(
private val settingsRepository: SettingsRepository,
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(KeyboardViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return KeyboardViewModel(settingsRepository) as T
}
throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.name}")
}
}
4 changes: 2 additions & 2 deletions app/src/main/java/com/rickyhu/hushkeyboard/main/MainApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.hilt.navigation.compose.hiltViewModel
import com.rickyhu.hushkeyboard.data.ThemeOption
import com.rickyhu.hushkeyboard.theme.HushKeyboardTheme
import org.koin.androidx.compose.koinViewModel

@Composable
fun MainApp(viewModel: MainViewModel = hiltViewModel()) {
fun MainApp(viewModel: MainViewModel = koinViewModel()) {
val state by viewModel.settingsState.collectAsState()

val isDarkTheme =
Expand Down
25 changes: 10 additions & 15 deletions app/src/main/java/com/rickyhu/hushkeyboard/main/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,16 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.rickyhu.hushkeyboard.data.AppSettings
import com.rickyhu.hushkeyboard.data.SettingsRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.stateIn
import javax.inject.Inject

@HiltViewModel
class MainViewModel
@Inject
constructor(
settingsRepository: SettingsRepository,
) : ViewModel() {
val settingsState =
settingsRepository.settings.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5000),
AppSettings(),
)
}
class MainViewModel(
settingsRepository: SettingsRepository,
) : ViewModel() {
val settingsState =
settingsRepository.settings.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5000),
AppSettings(),
)
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,34 @@
package com.rickyhu.hushkeyboard.service

import android.view.View
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
import androidx.lifecycle.setViewTreeLifecycleOwner
import androidx.savedstate.SavedStateRegistryController
import androidx.savedstate.SavedStateRegistryOwner
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import com.rickyhu.hushkeyboard.data.SettingsRepository
import com.rickyhu.hushkeyboard.keyboard.HushKeyboardView
import com.rickyhu.hushkeyboard.keyboard.KeyboardViewModel
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import com.rickyhu.hushkeyboard.keyboard.KeyboardViewModelFactory
import org.koin.android.ext.android.inject

@AndroidEntryPoint
class HushIMEService :
LifecycleInputMethodService(),
SavedStateRegistryOwner {
@Inject
SavedStateRegistryOwner,
ViewModelStoreOwner {
private val settingsRepository: SettingsRepository by inject()
lateinit var viewModel: KeyboardViewModel

private val savedStateRegistryController = SavedStateRegistryController.create(this)
override val savedStateRegistry = savedStateRegistryController.savedStateRegistry

override val lifecycle = dispatcher.lifecycle

private val _viewModelStore = ViewModelStore()
override val viewModelStore: ViewModelStore get() = _viewModelStore

override fun onCreateInputView(): View {
val view = HushKeyboardView(this)
window.window?.decorView.let { decorView ->
Expand All @@ -34,5 +41,15 @@ class HushIMEService :
override fun onCreate() {
super.onCreate()
savedStateRegistryController.performRestore(null)
viewModel =
ViewModelProvider(
owner = this,
factory = KeyboardViewModelFactory(settingsRepository),
)[KeyboardViewModel::class.java]
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

KeyboardViewModel is registered in the Koin module (AppModule.kt line 16) but is also being instantiated manually using a custom ViewModelFactory in HushIMEService. This creates two different instantiation paths for the same ViewModel, which could lead to confusion and maintenance issues. Consider whether the Koin registration is actually used anywhere, or if the manual factory approach is necessary due to IME service lifecycle constraints. If the Koin registration is not used, it should be removed to avoid confusion.

Copilot uses AI. Check for mistakes.
}

override fun onDestroy() {
super.onDestroy()
_viewModelStore.clear()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.navigation.compose.hiltViewModel
import com.rickyhu.hushkeyboard.data.ThemeOption
import com.rickyhu.hushkeyboard.data.WideNotationOption
import com.rickyhu.hushkeyboard.settings.ui.AddSpaceBetweenNotationSwitchItem
Expand All @@ -25,9 +24,10 @@ import com.rickyhu.hushkeyboard.settings.ui.ThemeOptionDropdownItem
import com.rickyhu.hushkeyboard.settings.ui.VibrateOnTapSwitchItem
import com.rickyhu.hushkeyboard.settings.ui.WideNotationOptionDropdownItem
import com.rickyhu.hushkeyboard.theme.HushKeyboardTheme
import org.koin.androidx.compose.koinViewModel

@Composable
fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
fun SettingsScreen(viewModel: SettingsViewModel = koinViewModel()) {
val state by viewModel.settingsState.collectAsState(SettingsState())

SettingsContent(
Expand Down
Loading