Skip to content

Commit

Permalink
Skip the "go to previous line on backspace" stuff that was just buggy…
Browse files Browse the repository at this point in the history
… anyway
  • Loading branch information
Eboreg committed Oct 27, 2023
1 parent 28925ea commit 219f476
Show file tree
Hide file tree
Showing 10 changed files with 49 additions and 102 deletions.
8 changes: 5 additions & 3 deletions app/src/main/java/us/huseli/retain/compose/HomeScreen.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package us.huseli.retain.compose

import androidx.activity.compose.BackHandler
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
Expand Down Expand Up @@ -44,7 +43,7 @@ import us.huseli.retain.data.entities.Note
import us.huseli.retain.viewmodels.NoteViewModel
import us.huseli.retain.viewmodels.SettingsViewModel

@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class)
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun HomeScreen(
modifier: Modifier = Modifier,
Expand Down Expand Up @@ -179,7 +178,10 @@ fun HomeScreen(
}
} else {
LazyColumn(
modifier = Modifier.reorderable(reorderableState).detectReorder(reorderableState),
modifier = Modifier
.reorderable(reorderableState)
.detectReorder(reorderableState)
.fillMaxHeight(),
state = reorderableState.listState,
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/java/us/huseli/retain/compose/NoteCard.kt
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ fun NoteCard(
isDragging: Boolean,
) {
val elevation by animateDpAsState(if (isDragging) 16.dp else 0.dp)
val noteColor = getNoteColor(note.color)
val defaultColor = MaterialTheme.colorScheme.background
val noteColor = getNoteColor(note.color, defaultColor)
val border =
if (isSelected) BorderStroke(3.dp, MaterialTheme.colorScheme.primary)
else BorderStroke(1.dp, MaterialTheme.colorScheme.onSurface.copy(alpha = 0.25f))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,10 @@ fun BaseNoteScreen(
val context = LocalContext.current
val images by viewModel.images.collectAsStateWithLifecycle()
val trashedImageCount by viewModel.trashedImageCount.collectAsStateWithLifecycle(0)
val noteColor by remember(color) { mutableStateOf(getNoteColor(context, color)) }
val appBarColor by remember(color) { mutableStateOf(getAppBarColor(context, color)) }
val background = MaterialTheme.colorScheme.background
val surface = MaterialTheme.colorScheme.surface
val noteColor by remember(color) { mutableStateOf(getNoteColor(context, color, background)) }
val appBarColor by remember(color) { mutableStateOf(getAppBarColor(context, color, surface)) }
val selectedImages by viewModel.selectedImages.collectAsStateWithLifecycle()
val imageAdded by viewModel.imageAdded.collectAsStateWithLifecycle(null)
val isImageSelectEnabled = selectedImages.isNotEmpty()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@file:Suppress("AnimateAsStateLabel")

package us.huseli.retain.compose.notescreen

import androidx.compose.animation.core.animateFloatAsState
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,6 @@ import androidx.compose.ui.unit.sp
import org.burnoutcrew.reorderable.ReorderableLazyListState
import org.burnoutcrew.reorderable.detectReorder

/**
* A row with a checkbox and a textfield. Multiple rows of these emulate
* multiline text editing behaviour, in that pressing enter in the middle of
* a row will bring the characters after the cursor down to a new row, and
* pressing backspace at the beginning of a row will paste the entire row
* contents to the end of the previous row.
*
* This is done via a hack where the textfield internally has an invisible
* null character at the beginning. Normally, there is no way for us to know
* that the user has pressed backspace when there are no characters before the
* cursor, but in this way, there actually are, even though it looks like the
* cursor is at the beginning of the row.
*/
@Composable
fun ChecklistNoteChecklistRow(
modifier: Modifier = Modifier,
Expand Down Expand Up @@ -104,23 +91,17 @@ fun ChecklistNoteChecklistRow(
onValueChange = onTextFieldValueChange,
keyboardOptions = KeyboardOptions.Default.copy(
imeAction = ImeAction.Next,
capitalization =
if (textFieldValue.selection.start == 1) KeyboardCapitalization.Characters
else KeyboardCapitalization.Sentences,
capitalization = KeyboardCapitalization.Sentences,
),
keyboardActions = KeyboardActions(
onNext = { onNext() }
),
modifier = Modifier
.onFocusChanged {
if (it.isFocused) onFocus()
}
.onFocusChanged { if (it.isFocused) onFocus() }
.weight(1f)
.fillMaxWidth()
.focusRequester(focusRequester)
.onGloballyPositioned {
if (isFocused) focusRequester.requestFocus()
},
.onGloballyPositioned { if (isFocused) focusRequester.requestFocus() },
)
IconButton(onClick = onDeleteClick) {
Icon(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ fun ChecklistNoteScreen(
val reorderableState = rememberReorderableLazyListState(
onMove = { from, to -> viewModel.switchItemPositions(from, to) },
)
val noteColor by remember(note.color) { mutableStateOf(getNoteColor(context, note.color)) }
val defaultColor = MaterialTheme.colorScheme.background
val noteColor by remember(note.color) { mutableStateOf(getNoteColor(context, note.color, defaultColor)) }

LaunchedEffect(trashedChecklistItems) {
if (trashedChecklistItems.isNotEmpty()) {
Expand Down Expand Up @@ -119,10 +120,7 @@ fun ChecklistNoteScreen(
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.clickable {
val index = uncheckedItems.size
viewModel.insertItem(text = "", checked = false, index = index)
}
.clickable { viewModel.insertItem(text = "", checked = false, index = uncheckedItems.size) }
.padding(vertical = 8.dp)
.fillMaxWidth()
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import androidx.compose.material.icons.sharp.SelectAll
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.TopAppBarDefaults
Expand Down Expand Up @@ -76,7 +77,7 @@ fun NoteScreenTopAppBar(
)
}
ColorDropdownMenu(
colors = getNoteColors(),
colors = getNoteColors(default = MaterialTheme.colorScheme.background),
isExpanded = isColorDropdownExpanded,
onDismiss = { isColorDropdownExpanded = false },
onColorClick = {
Expand Down
30 changes: 16 additions & 14 deletions app/src/main/java/us/huseli/retain/ui/theme/RetainColor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import us.huseli.retaintheme.ui.theme.RetainBasicColorsDark
import us.huseli.retaintheme.ui.theme.RetainBasicColorsLight
import kotlin.math.max

val noteColors: (RetainBasicColors) -> Map<String, Color> = { colorScheme ->
val noteColors: (RetainBasicColors, Color) -> Map<String, Color> = { colorScheme, default ->
mapOf(
"DEFAULT" to Color.Transparent,
"DEFAULT" to default,
"RED" to colorScheme.Red,
"ORANGE" to colorScheme.Orange,
"YELLOW" to colorScheme.Yellow,
Expand All @@ -28,25 +28,26 @@ val noteColors: (RetainBasicColors) -> Map<String, Color> = { colorScheme ->
}

@Composable
fun getNoteColors(): Map<String, Color> =
noteColors(if (isSystemInDarkTheme()) RetainBasicColorsDark else RetainBasicColorsLight)
fun getNoteColors(default: Color): Map<String, Color> =
noteColors(if (isSystemInDarkTheme()) RetainBasicColorsDark else RetainBasicColorsLight, default)

fun getNoteColor(key: String, dark: Boolean): Color {
fun getNoteColor(key: String, dark: Boolean, default: Color): Color {
val colorScheme = if (dark) RetainBasicColorsDark else RetainBasicColorsLight
return noteColors(colorScheme).getOrDefault(key, Color.Transparent)
return noteColors(colorScheme, default).getOrDefault(key, default)
}

fun getNoteColor(context: Context, key: String) = getNoteColor(
fun getNoteColor(context: Context, key: String, default: Color) = getNoteColor(
key = key,
dark = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
dark = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES,
default = default,
)

@Composable
fun getNoteColor(key: String): Color = getNoteColor(key, isSystemInDarkTheme())
fun getNoteColor(key: String, default: Color): Color = getNoteColor(key, isSystemInDarkTheme(), default)

fun getAppBarColor(key: String, dark: Boolean): Color {
return if (key == "DEFAULT") Color.Transparent
else getNoteColor(key, dark).let {
fun getAppBarColor(key: String, dark: Boolean, default: Color): Color {
return if (key == "DEFAULT") default
else getNoteColor(key, dark, default).let {
it.copy(
red = max(it.red - 0.05f, 0f),
green = max(it.green - 0.05f, 0f),
Expand All @@ -55,7 +56,8 @@ fun getAppBarColor(key: String, dark: Boolean): Color {
}
}

fun getAppBarColor(context: Context, key: String) = getAppBarColor(
fun getAppBarColor(context: Context, key: String, default: Color) = getAppBarColor(
key = key,
dark = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
dark = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES,
default = default,
)
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,22 @@ data class ChecklistItemFlow(
val checked: MutableStateFlow<Boolean> = MutableStateFlow(item.checked),
val position: MutableStateFlow<Int> = MutableStateFlow(item.position),
val textFieldValue: MutableStateFlow<TextFieldValue> = MutableStateFlow(
TextFieldValue(addNullChar(item.text), TextRange(1))
TextFieldValue(item.text, TextRange(0))
)
) {
override fun equals(other: Any?): Boolean {
return when (other) {
is ChecklistItemFlow ->
other.position.value == position.value &&
other.checked.value == checked.value &&
other.id == id &&
other.textFieldValue.value.text == textFieldValue.value.text
other.checked.value == checked.value &&
other.id == id &&
other.textFieldValue.value.text == textFieldValue.value.text

is ChecklistItem ->
other.id == id &&
other.position == position.value &&
other.checked == checked.value &&
other.text == textFieldValue.value.text
other.position == position.value &&
other.checked == checked.value &&
other.text == textFieldValue.value.text

else -> false
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,6 @@ import us.huseli.retain.data.NoteRepository
import us.huseli.retain.data.entities.ChecklistItem
import java.util.UUID
import javax.inject.Inject
import kotlin.math.max

const val INVISIBLE_CHAR = '\u0000'
fun adjustSelection(range: TextRange): TextRange = if (range.start < 1) TextRange(1, max(range.end, 1)) else range
fun stripNullChar(str: String): String = str.filter { it != INVISIBLE_CHAR }
fun addNullChar(str: String): String = INVISIBLE_CHAR + stripNullChar(str)

@HiltViewModel
class EditChecklistNoteViewModel @Inject constructor(
Expand Down Expand Up @@ -87,23 +81,6 @@ class EditChecklistNoteViewModel @Inject constructor(
fun insertItem(text: String, checked: Boolean, index: Int) =
insertItem(ChecklistItemFlow(ChecklistItem(text = text, checked = checked, noteId = _noteId, position = index)))

private fun mergeItemWithPrevious(item: ChecklistItemFlow) {
val items = _checklistItems.value.filter { it.checked.value == item.checked.value }.toMutableList()
val index = items.indexOfFirst { it.id == item.id }
val previousItem = items[index - 1]

if (item.textFieldValue.value.text.isNotEmpty()) {
updateItemTextFieldValue(
item = previousItem,
text = previousItem.textFieldValue.value.text + stripNullChar(item.textFieldValue.value.text),
selection = TextRange(previousItem.textFieldValue.value.text.length),
composition = previousItem.textFieldValue.value.composition,
)
}
_focusedItemId.value = previousItem.id
deleteItem(item, true)
}

fun onItemFocus(item: ChecklistItemFlow) {
_focusedItemId.value = item.id
}
Expand All @@ -120,31 +97,13 @@ class EditChecklistNoteViewModel @Inject constructor(
text = head,
selection = item.textFieldValue.value.selection,
composition = null,
// composition = item.textFieldValue.value.composition,
)
insertItem(tail, item.checked.value, index + 1)
}

fun onTextFieldValueChange(item: ChecklistItemFlow, textFieldValue: TextFieldValue) {
/**
* If the new TextFieldValue does not start with the null character,
* that must mean the user has just erased it by inputting a backspace
* at the beginning of the field. In that case, join this row with the
* one above. If this is the first row: just re-insert the null
* character and move the selection start to after it.
*/
if (item.id == _focusedItemId.value && textFieldValue != item.textFieldValue.value) {
log("onTextFieldValueChange: textFieldValue=$textFieldValue, item.textFieldValue.value=${item.textFieldValue.value}")
if (
textFieldValue.text.getOrNull(0) != INVISIBLE_CHAR &&
addNullChar(textFieldValue.text) == item.textFieldValue.value.text
) {
val index = _checklistItems.value
.filter { it.checked.value == item.checked.value }
.indexOfFirst { it.id == item.id }
if (index > 0) mergeItemWithPrevious(item)
else if (index == 0 && textFieldValue.text.isEmpty()) deleteItem(item, true)
} else updateItemTextFieldValue(
updateItemTextFieldValue(
item = item,
text = textFieldValue.text,
selection = textFieldValue.selection,
Expand Down Expand Up @@ -210,16 +169,15 @@ class EditChecklistNoteViewModel @Inject constructor(
selection: TextRange,
composition: TextRange?,
) {
log("updateItemTextFieldValue before: ${item.textFieldValue.value}, text = $text, selection = $selection, composition=$composition")
item.textFieldValue.value = item.textFieldValue.value.copy(
text = addNullChar(text),
selection = adjustSelection(selection),
text = text,
selection = selection,
composition = composition,
)
log("updateItemTextFieldValue after: ${item.textFieldValue.value}, text = $text, selection = $selection, composition=$composition")
_dirtyChecklistItems.removeIf { it.id == item.id }
if (_originalChecklistItems.none { it.id == item.id && it.text == stripNullChar(item.textFieldValue.value.text) })
if (_originalChecklistItems.none { it.id == item.id && it.text == item.textFieldValue.value.text }) {
_dirtyChecklistItems.add(item)
}
}

private fun updatePositions() {
Expand Down

0 comments on commit 219f476

Please sign in to comment.