Skip to content

Commit

Permalink
Merge pull request #127 from PhilKes/fix/edittext-changehistory
Browse files Browse the repository at this point in the history
Save note if any BaseNote fields changed + fix cursor position on undo/redo
  • Loading branch information
PhilKes authored Nov 16, 2024
2 parents c7c0c44 + b9235d8 commit 69bd1e8
Show file tree
Hide file tree
Showing 9 changed files with 87 additions and 47 deletions.
29 changes: 19 additions & 10 deletions app/src/main/java/com/philkes/notallyx/presentation/UiExtensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import com.philkes.notallyx.presentation.view.misc.Progress
import com.philkes.notallyx.presentation.view.note.listitem.ListManager
import com.philkes.notallyx.presentation.viewmodel.preference.DateFormat
import com.philkes.notallyx.utils.changehistory.ChangeHistory
import com.philkes.notallyx.utils.changehistory.EditTextState
import com.philkes.notallyx.utils.changehistory.EditTextWithHistoryChange
import java.io.File
import java.util.Date
Expand Down Expand Up @@ -94,6 +95,16 @@ fun String.applySpans(representations: List<SpanRepresentation>): Editable {
return editable
}

fun String.truncate(limit: Int): String {
return if (length > limit) {
val truncated = take(limit)
val remainingCharacters = length - limit
"$truncated... ($remainingCharacters more characters)"
} else {
this
}
}

/**
* Adjusts or removes spans based on the selection range.
*
Expand Down Expand Up @@ -220,10 +231,10 @@ fun Int.dp(context: Context): Int =
*/
fun EditText.createListTextWatcherWithHistory(listManager: ListManager, positionGetter: () -> Int) =
object : TextWatcher {
private lateinit var currentTextBefore: String
private lateinit var stateBefore: EditTextState

override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
currentTextBefore = s.toString()
stateBefore = EditTextState(getText()!!.clone(), selectionStart)
}

override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
Expand All @@ -233,8 +244,8 @@ fun EditText.createListTextWatcherWithHistory(listManager: ListManager, position
this@createListTextWatcherWithHistory,
this,
positionGetter.invoke(),
currentTextBefore,
requireNotNull(s).toString(),
EditTextState(getText()!!.clone(), selectionStart),
before = stateBefore,
)
}
}
Expand All @@ -245,26 +256,24 @@ fun EditTextWithHistory.createTextWatcherWithHistory(
updateModel: (text: Editable) -> Unit,
) =
object : TextWatcher {
private lateinit var currentTextBefore: Editable
private lateinit var stateBefore: EditTextState

override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
currentTextBefore = this@createTextWatcherWithHistory.getTextClone()
stateBefore = EditTextState(getTextClone(), selectionStart)
}

override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
onTextChanged?.invoke(s!!, start, count)
}

override fun afterTextChanged(s: Editable?) {
val textBefore = currentTextBefore.clone()
val textAfter = requireNotNull(s).clone()
updateModel.invoke(textAfter)

changeHistory.push(
EditTextWithHistoryChange(
this@createTextWatcherWithHistory,
textBefore,
textAfter,
stateBefore,
EditTextState(textAfter, selectionStart),
updateModel,
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ abstract class EditActivity(private val type: Type) : LockedActivity<ActivityEdi
lifecycleScope.launch(Dispatchers.Main) {
if (model.isEmpty()) {
model.deleteBaseNote()
} else if (changeHistory.canUndo()) {
} else if (model.isModified()) {
saveNote()
}
super.finish()
Expand All @@ -78,7 +78,7 @@ abstract class EditActivity(private val type: Type) : LockedActivity<ActivityEdi
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putLong("id", model.id)
if (changeHistory.canUndo()) {
if (model.isModified()) {
lifecycleScope.launch { saveNote() }
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.philkes.notallyx.presentation.clone
import com.philkes.notallyx.presentation.createTextWatcherWithHistory
import com.philkes.notallyx.presentation.removeSelectionFromSpan
import com.philkes.notallyx.utils.changehistory.ChangeHistory
import com.philkes.notallyx.utils.changehistory.EditTextState
import com.philkes.notallyx.utils.changehistory.EditTextWithHistoryChange

/**
Expand Down Expand Up @@ -80,6 +81,10 @@ class EditTextWithHistory(context: Context, attrs: AttributeSet) :
applyWithoutTextWatcher { super.setText(text, type) }
}

fun setText(text: Editable) {
super.setText(text, BufferType.EDITABLE)
}

fun applyWithoutTextWatcher(
callback: EditTextWithHistory.() -> Unit
): Pair<Editable, Editable> {
Expand Down Expand Up @@ -173,10 +178,16 @@ class EditTextWithHistory(context: Context, attrs: AttributeSet) :
* to [changeHistory]. This method is used by all other members functions.
*/
fun changeTextWithHistory(callback: (text: Editable) -> Unit) {
val cursorPosBefore = selectionStart
val (textBefore, textAfter) = changeText(callback)
updateModel?.invoke(textAfter.clone())
val textAfterClone = textAfter.clone()
updateModel?.invoke(textAfterClone)
changeHistory?.push(
EditTextWithHistoryChange(this, textBefore, textAfter) { text ->
EditTextWithHistoryChange(
this,
EditTextState(textBefore.clone(), cursorPosBefore),
EditTextState(textAfterClone, selectionStart),
) { text ->
updateModel?.invoke(text.clone())
}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import com.philkes.notallyx.presentation.viewmodel.preference.NotallyXPreference
import com.philkes.notallyx.utils.changehistory.ChangeCheckedForAllChange
import com.philkes.notallyx.utils.changehistory.ChangeHistory
import com.philkes.notallyx.utils.changehistory.DeleteCheckedChange
import com.philkes.notallyx.utils.changehistory.EditTextState
import com.philkes.notallyx.utils.changehistory.ListAddChange
import com.philkes.notallyx.utils.changehistory.ListCheckedChange
import com.philkes.notallyx.utils.changehistory.ListDeleteChange
Expand Down Expand Up @@ -221,15 +222,15 @@ class ListManager(
editText: EditText,
listener: TextWatcher,
position: Int,
textBefore: String,
textAfter: String,
value: EditTextState,
before: EditTextState? = null,
pushChange: Boolean = true,
) {
val item = items[position]
item.body = textAfter
item.body = value.text.toString()
if (pushChange) {
changeHistory.push(
ListEditTextChange(editText, position, textBefore, textAfter, listener, this)
ListEditTextChange(editText, position, before!!, value, listener, this)
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ class NotallyModel(private val app: Application) : AndroidViewModel(app) {
var audioRoot = app.getExternalAudioDirectory()
var filesRoot = app.getExternalFilesDirectory()

private lateinit var originalNote: BaseNote

init {
database.observeForever { baseNoteDao = it.getBaseNoteDao() }
}
Expand Down Expand Up @@ -208,6 +210,8 @@ class NotallyModel(private val app: Application) : AndroidViewModel(app) {
val baseNote = cachedNote ?: withContext(Dispatchers.IO) { baseNoteDao.get(id) }

if (baseNote != null) {
originalNote = baseNote

this.id = id
folder = baseNote.folder
color = baseNote.color
Expand All @@ -228,14 +232,16 @@ class NotallyModel(private val app: Application) : AndroidViewModel(app) {
files.value = baseNote.files
audios.value = baseNote.audios
} else {
createBaseNote()
originalNote = createBaseNote()
Toast.makeText(app, R.string.cant_find_note, Toast.LENGTH_LONG).show()
}
} else createBaseNote()
} else originalNote = createBaseNote()
}

private suspend fun createBaseNote() {
id = withContext(Dispatchers.IO) { baseNoteDao.insert(getBaseNote()) }
private suspend fun createBaseNote(): BaseNote {
val baseNote = getBaseNote()
id = withContext(Dispatchers.IO) { baseNoteDao.insert(baseNote) }
return baseNote.copy(id = id)
}

suspend fun deleteBaseNote() {
Expand Down Expand Up @@ -266,6 +272,10 @@ class NotallyModel(private val app: Application) : AndroidViewModel(app) {
audios.value.isEmpty()
}

fun isModified(): Boolean {
return getBaseNote() != originalNote
}

private suspend fun updateImages() {
withContext(Dispatchers.IO) { baseNoteDao.updateImages(id, images.value) }
}
Expand All @@ -280,7 +290,7 @@ class NotallyModel(private val app: Application) : AndroidViewModel(app) {

private fun getBaseNote(): BaseNote {
val spans = getFilteredSpans(body)
val body = this.body.trimEnd().toString()
val body = this.body.toString()
val nonEmptyItems = this.items.filter { item -> item.body.isNotEmpty() }
return BaseNote(
id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@ import com.philkes.notallyx.presentation.view.misc.EditTextWithHistory

class EditTextWithHistoryChange(
private val editText: EditTextWithHistory,
textBefore: Editable,
textAfter: Editable,
before: EditTextState,
after: EditTextState,
private val updateModel: (newValue: Editable) -> Unit,
) : ValueChange<Editable>(textAfter, textBefore) {
) : ValueChange<EditTextState>(after, before) {

private val cursorPosition = editText.selectionStart

override fun update(value: Editable, isUndo: Boolean) {
override fun update(value: EditTextState, isUndo: Boolean) {
editText.applyWithoutTextWatcher {
text = value
updateModel.invoke(value)
setText(value.text)
updateModel.invoke(value.text)
requestFocus()
setSelection(Math.min(value.length, cursorPosition + (if (isUndo) 1 else 0)))
setSelection(value.cursorPos)
}
}
}

data class EditTextState(val text: Editable, val cursorPos: Int)
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,20 @@ import com.philkes.notallyx.presentation.view.note.listitem.ListManager
open class ListEditTextChange(
private val editText: EditText,
position: Int,
private val textBefore: String,
private val textAfter: String,
before: EditTextState,
after: EditTextState,
private val listener: TextWatcher,
private val listManager: ListManager,
) : ListPositionValueChange<String>(textAfter, textBefore, position) {
private val cursorPosition = editText.selectionStart
) : ListPositionValueChange<EditTextState>(after, before, position) {

override fun update(position: Int, value: String, isUndo: Boolean) {
listManager.changeText(editText, listener, position, textBefore, value, pushChange = false)
editText.removeTextChangedListener(listener)
editText.setText(value)
editText.requestFocus()
editText.setSelection(Math.max(0, cursorPosition - (if (isUndo) 1 else 0)))
editText.addTextChangedListener(listener)
}

override fun toString(): String {
return "CheckedText at $position from: $textBefore to: $textAfter"
override fun update(position: Int, value: EditTextState, isUndo: Boolean) {
listManager.changeText(editText, listener, position, value, pushChange = false)
editText.apply {
removeTextChangedListener(listener)
text = value.text
requestFocus()
setSelection(value.cursorPos)
addTextChangedListener(listener)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.philkes.notallyx.utils.changehistory

import com.philkes.notallyx.presentation.truncate

abstract class ListPositionValueChange<T>(
internal val newValue: T,
internal val oldValue: T,
Expand All @@ -15,4 +17,8 @@ abstract class ListPositionValueChange<T>(
}

abstract fun update(position: Int, value: T, isUndo: Boolean)

override fun toString(): String {
return "${javaClass.simpleName} at $position from: ${oldValue.toString().truncate(100)} to: ${newValue.toString().truncate(100)}"
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.philkes.notallyx.utils.changehistory

import com.philkes.notallyx.presentation.truncate

abstract class ValueChange<T>(protected val newValue: T, protected val oldValue: T) : Change {

override fun redo() {
Expand All @@ -11,4 +13,8 @@ abstract class ValueChange<T>(protected val newValue: T, protected val oldValue:
}

abstract fun update(value: T, isUndo: Boolean)

override fun toString(): String {
return "${javaClass.simpleName} from: ${oldValue.toString().truncate(100)} to: ${newValue.toString().truncate(100)}"
}
}

0 comments on commit 69bd1e8

Please sign in to comment.