Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 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
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import kotlinx.serialization.Serializable
@Serializable
data class AppSettings(
val themeOption: ThemeOption = ThemeOption.System,
val wideNotationOption: WideNotationOption = WideNotationOption.WideWithW,
val smartDelete: Boolean = true,
val addSpaceAfterNotation: Boolean = true,
val vibrateOnTap: Boolean = true,
val wideNotationOption: WideNotationOption = WideNotationOption.WideWithW
val vibrateOnTap: Boolean = true
)

enum class ThemeOption { System, Light, Dark }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ class SettingsRepository @Inject constructor(
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) }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.rickyhu.hushkeyboard.keyboard
import android.content.Context
import android.os.Build
import android.os.VibratorManager
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.annotation.VisibleForTesting
import androidx.compose.foundation.background
Expand Down Expand Up @@ -34,9 +35,12 @@ import com.rickyhu.hushkeyboard.theme.LightBackground
import com.rickyhu.hushkeyboard.utils.deleteText
import com.rickyhu.hushkeyboard.utils.inputText
import com.rickyhu.hushkeyboard.utils.maybeVibrate
import com.rickyhu.hushkeyboard.utils.smartDelete
import com.rickyhu.hushkeyboard.utils.toInputConnection
import splitties.systemservices.inputMethodManager

private const val TAG = "HushKeyboardView"

class HushKeyboardView(context: Context) : AbstractComposeView(context) {

@RequiresApi(Build.VERSION_CODES.S)
Expand Down Expand Up @@ -80,6 +84,7 @@ fun HushKeyboardContent(state: KeyboardState) {
addSpaceAfterNotation = state.addSpaceAfterNotation,
wideNotationOption = state.wideNotationOption,
onTextInput = {
Log.d(TAG, "Notation key tapped")
context.toInputConnection().inputText(it)
if (state.vibrateOnTap) vibratorManager?.maybeVibrate()
}
Expand All @@ -99,17 +104,21 @@ fun HushKeyboardContent(state: KeyboardState) {
ControlKeyButtonRow(
turns = keyConfigState.turns,
isDarkTheme = isDarkTheme,
smartDelete = state.smartDelete,
inputMethodButtonAction = {
Log.d(TAG, "Input method picker tapped")
inputMethodManager.showInputMethodPicker()
if (state.vibrateOnTap) vibratorManager?.maybeVibrate()
},
rotateDirectionButtonAction = {
Log.d(TAG, "Rotate direction button tapped")
keyConfigState = keyConfigState.copy(
isCounterClockwise = !keyConfigState.isCounterClockwise
)
if (state.vibrateOnTap) vibratorManager?.maybeVibrate()
},
turnDegreeButtonAction = {
Log.d(TAG, "Turn degree button tapped")
keyConfigState = when (keyConfigState.turns) {
Turns.Single -> keyConfigState.copy(turns = Turns.Double)
Turns.Double -> keyConfigState.copy(turns = Turns.Triple)
Expand All @@ -118,16 +127,27 @@ fun HushKeyboardContent(state: KeyboardState) {
if (state.vibrateOnTap) vibratorManager?.maybeVibrate()
},
wideTurnButtonAction = {
Log.d(TAG, "Wide turn button tapped")
keyConfigState = keyConfigState.copy(
isWideTurn = !keyConfigState.isWideTurn
)
if (state.vibrateOnTap) vibratorManager?.maybeVibrate()
},
deleteButtonAction = {
context.toInputConnection().deleteText()
if (state.vibrateOnTap) vibratorManager?.maybeVibrate()
deleteButtonAction = if (state.smartDelete) {
{
Log.d(TAG, "Delete button tapped")
context.toInputConnection().smartDelete()
if (state.vibrateOnTap) vibratorManager?.maybeVibrate()
}
} else {
{
Log.d(TAG, "Smart delete button tapped")
Copy link

Copilot AI Jul 23, 2025

Choose a reason for hiding this comment

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

The log message says "Smart delete button tapped" when this is actually the regular delete functionality (when smart delete is disabled). This should be "Delete button tapped" to match the actual functionality.

Suggested change
Log.d(TAG, "Smart delete button tapped")
Log.d(TAG, "Delete button tapped")

Copilot uses AI. Check for mistakes.
context.toInputConnection().deleteText()
if (state.vibrateOnTap) vibratorManager?.maybeVibrate()
}
},
newLineButtonAction = {
Log.d(TAG, "New line button tapped")
context.toInputConnection().inputText("\n")
if (state.vibrateOnTap) vibratorManager?.maybeVibrate()
}
Expand All @@ -142,9 +162,10 @@ fun HushKeyboardPreview() {
HushKeyboardContent(
state = KeyboardState(
themeOption = ThemeOption.System,
wideNotationOption = WideNotationOption.WideWithW,
smartDelete = true,
addSpaceAfterNotation = true,
vibrateOnTap = true,
wideNotationOption = WideNotationOption.WideWithW
vibrateOnTap = true
)
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,18 @@ class KeyboardViewModel @Inject constructor(
).map { settings ->
KeyboardState(
themeOption = settings.themeOption,
wideNotationOption = settings.wideNotationOption,
smartDelete = settings.smartDelete,
addSpaceAfterNotation = settings.addSpaceAfterNotation,
vibrateOnTap = settings.vibrateOnTap,
wideNotationOption = settings.wideNotationOption
vibrateOnTap = settings.vibrateOnTap
)
}
}

data class KeyboardState(
val themeOption: ThemeOption = ThemeOption.System,
val wideNotationOption: WideNotationOption = WideNotationOption.WideWithW,
val smartDelete: Boolean = true,
val addSpaceAfterNotation: Boolean = true,
val vibrateOnTap: Boolean = true,
val wideNotationOption: WideNotationOption = WideNotationOption.WideWithW
val vibrateOnTap: Boolean = true
)
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ fun ControlKeyButtonRow(
modifier: Modifier = Modifier,
turns: Turns,
isDarkTheme: Boolean,
smartDelete: Boolean,
inputMethodButtonAction: () -> Unit,
rotateDirectionButtonAction: () -> Unit,
turnDegreeButtonAction: () -> Unit,
Expand Down Expand Up @@ -94,16 +95,20 @@ fun ControlKeyButtonRow(
)
}
)

ControlKeyButton(
modifier = controlKeyModifier.testTag("DeleteButton"),
onClick = deleteButtonAction,
isDarkTheme = isDarkTheme,
content = {
Text(
"⌫",
color = keyColor,
fontSize = 18.sp,
textAlign = TextAlign.Center
Icon(
painter = if (smartDelete) {
painterResource(R.drawable.ic_backspace_filled)
} else {
painterResource(R.drawable.ic_backspace_outlined)
},
tint = keyColor,
contentDescription = "Delete"
)
}
)
Expand All @@ -129,6 +134,7 @@ private fun ControlKeyButtonRowPreview() {
ControlKeyButtonRow(
turns = Turns.Single,
isDarkTheme = false,
smartDelete = true,
inputMethodButtonAction = {},
rotateDirectionButtonAction = {},
turnDegreeButtonAction = {},
Expand Down
6 changes: 5 additions & 1 deletion app/src/main/java/com/rickyhu/hushkeyboard/model/Notation.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,9 @@ enum class Notation(val value: String) {
S("S"),
X("x"),
Y("y"),
Z("z")
Z("z");

companion object {
fun getCharList(): List<Char> = Notation.entries.map { it.value.single() }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import com.rickyhu.hushkeyboard.data.ThemeOption
import com.rickyhu.hushkeyboard.data.WideNotationOption
import com.rickyhu.hushkeyboard.settings.ui.AddSpaceBetweenNotationSwitchItem
import com.rickyhu.hushkeyboard.settings.ui.AppVersionItem
import com.rickyhu.hushkeyboard.settings.ui.SmartDeleteSwitchItem
import com.rickyhu.hushkeyboard.settings.ui.ThemeOptionDropdownItem
import com.rickyhu.hushkeyboard.settings.ui.VibrateOnTapSwitchItem
import com.rickyhu.hushkeyboard.settings.ui.WideNotationOptionDropdownItem
Expand All @@ -35,6 +36,7 @@ fun SettingsScreen(
state,
onThemeSelected = viewModel::updateThemeOption,
onWideNotationOptionSelected = viewModel::updateWideNotationOption,
onSmartDeleteChanged = viewModel::updateSmartDelete,
onAddSpaceBetweenNotationChanged = viewModel::updateAddSpaceBetweenNotation,
onVibrateOnTapChanged = viewModel::updateVibrateOnTap
)
Expand All @@ -47,6 +49,7 @@ fun SettingsContent(
state: SettingsState,
onThemeSelected: (themeOption: ThemeOption) -> Unit,
onWideNotationOptionSelected: (wideNotationOption: WideNotationOption) -> Unit,
onSmartDeleteChanged: (smartDelete: Boolean) -> Unit,
onAddSpaceBetweenNotationChanged: (addSpaceAfterNotation: Boolean) -> Unit,
onVibrateOnTapChanged: (vibrateOnTap: Boolean) -> Unit
) {
Expand All @@ -66,6 +69,10 @@ fun SettingsContent(
currentOption = state.wideNotationOption,
onOptionSelected = onWideNotationOptionSelected
)
SmartDeleteSwitchItem(
value = state.smartDelete,
onValueChanged = onSmartDeleteChanged
)
AddSpaceBetweenNotationSwitchItem(
value = state.addSpaceAfterNotation,
onValueChanged = onAddSpaceBetweenNotationChanged
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class SettingsViewModel @Inject constructor(
SettingsState(
themeOption = settings.themeOption,
addSpaceAfterNotation = settings.addSpaceAfterNotation,
smartDelete = settings.smartDelete,
vibrateOnTap = settings.vibrateOnTap,
wideNotationOption = settings.wideNotationOption
)
Expand All @@ -43,6 +44,12 @@ class SettingsViewModel @Inject constructor(
}
}

fun updateSmartDelete(smartDelete: Boolean) {
viewModelScope.launch {
settingsRepository.updateSmartDelete(smartDelete)
}
}

fun updateAddSpaceBetweenNotation(addSpaceBetweenNotation: Boolean) {
viewModelScope.launch {
settingsRepository.updateAddSpaceBetweenNotation(addSpaceBetweenNotation)
Expand All @@ -58,7 +65,8 @@ class SettingsViewModel @Inject constructor(

data class SettingsState(
val themeOption: ThemeOption = ThemeOption.System,
val wideNotationOption: WideNotationOption = WideNotationOption.WideWithW,
val smartDelete: Boolean = true,
val addSpaceAfterNotation: Boolean = true,
val vibrateOnTap: Boolean = true,
val wideNotationOption: WideNotationOption = WideNotationOption.WideWithW
val vibrateOnTap: Boolean = true
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.rickyhu.hushkeyboard.settings.ui

import androidx.compose.foundation.clickable
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import com.rickyhu.hushkeyboard.R
import com.rickyhu.hushkeyboard.theme.HushKeyboardTheme

@Composable
fun SmartDeleteSwitchItem(
value: Boolean,
onValueChanged: (Boolean) -> Unit = {}
) {
ListItem(
modifier = Modifier.clickable { onValueChanged(!value) },
headlineContent = { Text("Smart Delete") },
leadingContent = {
Icon(
painter = painterResource(R.drawable.ic_backspace_filled),
contentDescription = "Delete"
)
},
trailingContent = {
Switch(
checked = value,
onCheckedChange = onValueChanged,
modifier = Modifier.testTag("SmartDeleteSwitchItem")
)
}
)
}

@Preview(showBackground = true)
@Composable
fun SmartDeleteSwitchItemPreview() {
HushKeyboardTheme {
AddSpaceBetweenNotationSwitchItem(
Copy link

Copilot AI Jul 23, 2025

Choose a reason for hiding this comment

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

The preview function is calling AddSpaceBetweenNotationSwitchItem instead of SmartDeleteSwitchItem. This will cause the preview to display the wrong component.

Suggested change
AddSpaceBetweenNotationSwitchItem(
SmartDeleteSwitchItem(

Copilot uses AI. Check for mistakes.
value = true
)
}
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,68 @@
package com.rickyhu.hushkeyboard.utils

import android.content.Context
import android.util.Log
import android.view.inputmethod.InputConnection
import com.rickyhu.hushkeyboard.model.Notation
import com.rickyhu.hushkeyboard.service.HushIMEService

private const val CURSOR_POSITION = 1
private const val TAG = "InputConnection"
private const val END_CURSOR_POSITION = 1

val notationCharList = Notation.getCharList() + listOf('\n')

Copy link

Copilot AI Jul 23, 2025

Choose a reason for hiding this comment

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

[nitpick] The inclusion of newline character ('\n') in the notation list seems inconsistent with the purpose of cube notations. Consider creating a separate constant or adding a comment explaining why newline is treated as a notation character for smart delete purposes.

Suggested change
val notationCharList = Notation.getCharList() + listOf('\n')
// Newline character is included to handle smart delete functionality, allowing deletion of line breaks.
private const val NEWLINE_CHAR = '\n'
val notationCharList = Notation.getCharList() + listOf(NEWLINE_CHAR)

Copilot uses AI. Check for mistakes.
fun Context.toInputConnection(): InputConnection = (this as HushIMEService).currentInputConnection

fun InputConnection.inputText(text: String) {
commitText(text, CURSOR_POSITION)
commitText(text, END_CURSOR_POSITION)
Log.d(TAG, "inputText $text")
}

fun InputConnection.deleteText() {
val selectedText = getSelectedText(0)

if (selectedText.isNullOrEmpty()) {
deleteSurroundingText(1, 0)
Log.d(TAG, "deleteText")
} else {
commitText("", CURSOR_POSITION)
commitText("", END_CURSOR_POSITION)
Log.d(TAG, "delete selected text: $selectedText")
}
}

fun InputConnection.smartDelete() {
Log.d(TAG, "smartDelete")

val selectedText = getSelectedText(0)
if (!selectedText.isNullOrEmpty()) {
commitText("", END_CURSOR_POSITION)
Log.d(TAG, "delete selected text: $selectedText")
return
}

beginBatchEdit()
try {
val scanWindow = 50
Copy link

Copilot AI Jul 23, 2025

Choose a reason for hiding this comment

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

The magic number 50 for scan window size should be extracted to a named constant to improve code maintainability and make it easier to adjust if needed.

Suggested change
val scanWindow = 50
val scanWindow = SCAN_WINDOW_SIZE

Copilot uses AI. Check for mistakes.
val textBeforeCursor = getTextBeforeCursor(scanWindow, 0)

if (textBeforeCursor.isNullOrEmpty()) {
deleteSurroundingText(1, 0)
return
}

var charsToDelete = 0
for (i in textBeforeCursor.indices.reversed()) {
val char = textBeforeCursor[i]
charsToDelete++
if (char.uppercaseChar() in notationCharList) {
break
}
}

if (charsToDelete > 0) {
deleteSurroundingText(charsToDelete, 0)
}
} finally {
endBatchEdit()
}
}
5 changes: 5 additions & 0 deletions app/src/main/res/drawable/ic_backspace_filled.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">

<path android:fillColor="@android:color/white" android:pathData="M22,3L7,3c-0.69,0 -1.23,0.35 -1.59,0.88L0,12l5.41,8.11c0.36,0.53 0.9,0.89 1.59,0.89h15c1.1,0 2,-0.9 2,-2L24,5c0,-1.1 -0.9,-2 -2,-2zM19,15.59L17.59,17 14,13.41 10.41,17 9,15.59 12.59,12 9,8.41 10.41,7 14,10.59 17.59,7 19,8.41 15.41,12 19,15.59z"/>

</vector>
5 changes: 5 additions & 0 deletions app/src/main/res/drawable/ic_backspace_outlined.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">

<path android:fillColor="@android:color/white" android:pathData="M22,3L7,3c-0.69,0 -1.23,0.35 -1.59,0.88L0,12l5.41,8.11c0.36,0.53 0.9,0.89 1.59,0.89h15c1.1,0 2,-0.9 2,-2L24,5c0,-1.1 -0.9,-2 -2,-2zM22,19L7.07,19L2.4,12l4.66,-7L22,5v14zM10.41,17L14,13.41 17.59,17 19,15.59 15.41,12 19,8.41 17.59,7 14,10.59 10.41,7 9,8.41 12.59,12 9,15.59z"/>

</vector>
Loading