1
0
Fork 0
mirror of https://github.com/anyproto/anytype-kotlin.git synced 2025-06-08 05:47:05 +09:00

Refactor BlockViewDiffUtil (#190)

This commit is contained in:
Evgenii Kozlov 2020-02-05 18:16:56 +03:00 committed by GitHub
parent f1422b8506
commit fdfd8a488f
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 153 additions and 194 deletions

View file

@ -1,13 +1,31 @@
# Change log for Android @Anytype app.
## Version 0.0.18 (WIP)
### New features
*
### Design
*
### Fixes && tech
* Refactored `BlockViewDiffUtil` (processing change payload) (#164)
### Middleware
*
## Version 0.0.17
### New features
* User can now use the color toolbar to change the text color of the whole text block (#153)
* User can now user the markup-color toolbar to change the background color of the selected text (#111)
* Create checkbox-list item on enter-pressed-event (instead of a simple paragraph) (#155)
* Create bulleted-list item on enter-pressed-event (instead of a simple paragraph) (#154)
* User can now use the markup-color toolbar to change the background color of the selected text (#111)
* Create a checkbox-list item on enter-pressed-event (instead of a simple paragraph) (#155)
* Create a bulleted-list item on enter-pressed-event (instead of a simple paragraph) (#154)
* `Block.Content.Text` model now has optional `color` property (#153).
* Added documentation engine (`DOKKA`) for `domain` module: documentation is generated automatically from KDoc (#168).
* Added new content model: `Block.Content.Link` (#173)

View file

@ -0,0 +1,8 @@
package com.agileburo.anytype.core_ui.common
/**
* Defines a view that can be checked
*/
interface Checkable {
val isChecked: Boolean
}

View file

@ -24,6 +24,7 @@ import com.agileburo.anytype.core_ui.features.page.BlockViewHolder.Companion.HOL
import com.agileburo.anytype.core_ui.features.page.BlockViewHolder.Companion.HOLDER_TASK
import com.agileburo.anytype.core_ui.features.page.BlockViewHolder.Companion.HOLDER_TITLE
import com.agileburo.anytype.core_ui.features.page.BlockViewHolder.Companion.HOLDER_TOGGLE
import com.agileburo.anytype.core_utils.ext.typeOf
import timber.log.Timber
/**
@ -230,20 +231,20 @@ class BlockAdapter(
when (holder) {
is BlockViewHolder.Paragraph -> {
holder.processChangePayload(
payloads = payloads,
item = blocks[position] as BlockView.Paragraph
payloads = payloads.typeOf(),
item = blocks[position]
)
}
is BlockViewHolder.Bulleted -> {
holder.processChangePayload(
payloads = payloads,
item = blocks[position] as BlockView.Bulleted
payloads = payloads.typeOf(),
item = blocks[position]
)
}
is BlockViewHolder.Checkbox -> {
holder.processChangePayload(
payloads = payloads,
item = blocks[position] as BlockView.Checkbox
payloads = payloads.typeOf(),
item = blocks[position]
)
}
else -> TODO()

View file

@ -1,5 +1,6 @@
package com.agileburo.anytype.core_ui.features.page
import com.agileburo.anytype.core_ui.common.Checkable
import com.agileburo.anytype.core_ui.common.Focusable
import com.agileburo.anytype.core_ui.common.Markup
import com.agileburo.anytype.core_ui.common.ViewType
@ -152,15 +153,16 @@ sealed class BlockView : ViewType {
* UI-model for checkbox blocks.
* @property id block's id
* @property text checkbox's content text
* @property checked immutable checkbox state (whether this checkbox is checked or not)
* @property isChecked immutable checkbox state (whether this checkbox is checked or not)
*/
data class Checkbox(
override val id: String,
override val marks: List<Markup.Mark> = emptyList(),
override val focused: Boolean = false,
val text: String,
val checked: Boolean = false
) : BlockView(), Markup, Focusable {
override val text: String,
override val color: String? = null,
override val isChecked: Boolean = false
) : BlockView(), Markup, Focusable, Text, Checkable {
override fun getViewType() = HOLDER_CHECKBOX
override val body: String = text
}

View file

@ -1,6 +1,8 @@
package com.agileburo.anytype.core_ui.features.page
import androidx.recyclerview.widget.DiffUtil
import com.agileburo.anytype.core_ui.common.Focusable
import com.agileburo.anytype.core_ui.common.Markup
class BlockViewDiffUtil(
private val old: List<BlockView>,
@ -22,81 +24,48 @@ class BlockViewDiffUtil(
override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
// TODO refactoring needed. Return list of changes instead of one change.
val oldBlock = old[oldItemPosition]
val newBlock = new[newItemPosition]
return if (oldBlock is BlockView.Paragraph && newBlock is BlockView.Paragraph) {
if (oldBlock.text != newBlock.text) {
if (oldBlock.marks != newBlock.marks)
if (oldBlock.color != newBlock.color) {
TEXT_MARKUP_AND_COLOR_CHANGED
} else {
TEXT_AND_MARKUP_CHANGED
}
else if (oldBlock.color != newBlock.color) {
TEXT_AND_COLOR_CHANGED
} else
TEXT_CHANGED
} else {
when {
oldBlock.marks != newBlock.marks && oldBlock.color != newBlock.color -> MARKUP_AND_COLOR_CHANGED
oldBlock.focused != newBlock.focused && oldBlock.color != newBlock.color -> FOCUS_AND_COLOR_CHANGED
oldBlock.marks != newBlock.marks -> MARKUP_CHANGED
oldBlock.focused != newBlock.focused -> FOCUS_CHANGED
oldBlock.color != newBlock.color -> TEXT_COLOR_CHANGED
else -> throw IllegalStateException("Unexpected change payload scenario:\n$oldBlock\n$newBlock")
}
}
} else if (oldBlock is BlockView.Bulleted && newBlock is BlockView.Bulleted) {
if (oldBlock.text != newBlock.text) {
if (oldBlock.marks != newBlock.marks)
if (oldBlock.color != newBlock.color) {
TEXT_MARKUP_AND_COLOR_CHANGED
} else {
TEXT_AND_MARKUP_CHANGED
}
else if (oldBlock.color != newBlock.color) {
TEXT_AND_COLOR_CHANGED
} else
TEXT_CHANGED
} else {
when {
oldBlock.marks != newBlock.marks && oldBlock.color != newBlock.color -> MARKUP_AND_COLOR_CHANGED
oldBlock.focused != newBlock.focused && oldBlock.color != newBlock.color -> FOCUS_AND_COLOR_CHANGED
oldBlock.marks != newBlock.marks -> MARKUP_CHANGED
oldBlock.focused != newBlock.focused -> FOCUS_CHANGED
oldBlock.color != newBlock.color -> TEXT_COLOR_CHANGED
else -> throw IllegalStateException("Unexpected change payload scenario:\n$oldBlock\n$newBlock")
}
}
} else if (oldBlock is BlockView.Checkbox && newBlock is BlockView.Checkbox) {
if (oldBlock.text != newBlock.text) {
if (oldBlock.marks != newBlock.marks)
TEXT_AND_MARKUP_CHANGED
else
TEXT_CHANGED
} else {
when {
oldBlock.marks != newBlock.marks -> MARKUP_CHANGED
oldBlock.focused != newBlock.focused -> FOCUS_CHANGED
else -> throw IllegalStateException("Unexpected change payload scenario:\n$oldBlock\n$newBlock")
}
}
} else
if (newBlock::class != oldBlock::class)
return super.getChangePayload(oldItemPosition, newItemPosition)
val changes = mutableListOf<Int>()
if (newBlock is BlockView.Text && oldBlock is BlockView.Text) {
if (newBlock.text != oldBlock.text)
changes.add(TEXT_CHANGED)
if (newBlock.color != oldBlock.color)
changes.add(TEXT_COLOR_CHANGED)
}
if (newBlock is Markup && oldBlock is Markup) {
if (newBlock.marks != oldBlock.marks)
changes.add(MARKUP_CHANGED)
}
if (newBlock is Focusable && oldBlock is Focusable) {
if (newBlock.focused != oldBlock.focused)
changes.add(FOCUS_CHANGED)
}
return if (changes.isNotEmpty())
Payload(changes)
else
super.getChangePayload(oldItemPosition, newItemPosition)
}
/**
* Payload of changes to apply to a block view.
*/
data class Payload(
val changes: List<Int>
)
companion object {
const val TEXT_CHANGED = 0
const val MARKUP_CHANGED = 1
const val TEXT_AND_MARKUP_CHANGED = 2
const val FOCUS_CHANGED = 3
const val TEXT_COLOR_CHANGED = 4
const val TEXT_MARKUP_AND_COLOR_CHANGED = 5
const val TEXT_AND_COLOR_CHANGED = 6
const val FOCUS_AND_COLOR_CHANGED = 7
const val MARKUP_AND_COLOR_CHANGED = 8
}
}

View file

@ -9,14 +9,11 @@ import com.agileburo.anytype.core_ui.R
import com.agileburo.anytype.core_ui.common.*
import com.agileburo.anytype.core_ui.extensions.color
import com.agileburo.anytype.core_ui.extensions.tint
import com.agileburo.anytype.core_ui.features.page.BlockViewDiffUtil.Companion.FOCUS_AND_COLOR_CHANGED
import com.agileburo.anytype.core_ui.features.page.BlockViewDiffUtil.Companion.FOCUS_CHANGED
import com.agileburo.anytype.core_ui.features.page.BlockViewDiffUtil.Companion.MARKUP_CHANGED
import com.agileburo.anytype.core_ui.features.page.BlockViewDiffUtil.Companion.TEXT_AND_COLOR_CHANGED
import com.agileburo.anytype.core_ui.features.page.BlockViewDiffUtil.Companion.TEXT_AND_MARKUP_CHANGED
import com.agileburo.anytype.core_ui.features.page.BlockViewDiffUtil.Companion.TEXT_CHANGED
import com.agileburo.anytype.core_ui.features.page.BlockViewDiffUtil.Companion.TEXT_COLOR_CHANGED
import com.agileburo.anytype.core_ui.features.page.BlockViewDiffUtil.Companion.TEXT_MARKUP_AND_COLOR_CHANGED
import com.agileburo.anytype.core_ui.features.page.BlockViewDiffUtil.Payload
import com.agileburo.anytype.core_ui.tools.DefaultSpannableFactory
import com.agileburo.anytype.core_ui.tools.DefaultTextWatcher
import com.agileburo.anytype.core_ui.widgets.text.TextInputWidget
@ -111,6 +108,33 @@ sealed class BlockViewHolder(view: View) : RecyclerView.ViewHolder(view) {
)
}
}
fun processChangePayload(
payloads: List<Payload>,
item: BlockView
) = payloads.forEach { payload ->
Timber.d("Processing $payload")
if (item is BlockView.Text) {
if (payload.changes.contains(TEXT_CHANGED))
if (content.text.toString() != item.text)
content.setText(item.text)
if (payload.changes.contains(TEXT_COLOR_CHANGED))
item.color?.let { setTextColor(it) }
}
if (item is Markup) {
if (payload.changes.contains(MARKUP_CHANGED))
setMarkup(item)
}
if (item is Focusable) {
if (payload.changes.contains(FOCUS_CHANGED))
setFocus(item)
}
}
}
class Paragraph(view: View) : BlockViewHolder(view), TextHolder {
@ -155,53 +179,6 @@ sealed class BlockViewHolder(view: View) : RecyclerView.ViewHolder(view) {
}
content.selectionDetector = { onSelectionChanged(item.id, it) }
}
fun processChangePayload(
payloads: List<Any>,
item: BlockView.Paragraph
) = payloads.forEach { payload ->
Timber.d("Applying change payload: $payload")
when (payload) {
MARKUP_CHANGED -> {
if (item.marks.isLinksPresent()) {
content.setLinksClickable()
}
setMarkup(markup = item)
}
TEXT_CHANGED -> {
if (content.text.toString() != item.text)
content.setText(item.text)
}
TEXT_AND_MARKUP_CHANGED -> {
if (item.marks.isLinksPresent()) {
content.setLinksClickable()
}
if (content.text.toString() != item.text)
content.setText(item.text)
setMarkup(markup = item)
}
TEXT_MARKUP_AND_COLOR_CHANGED -> {
if (item.color != null) setTextColor(item.color)
if (content.text.toString() != item.text) content.setText(item.text)
setMarkup(markup = item)
}
FOCUS_CHANGED -> {
setFocus(item)
}
TEXT_COLOR_CHANGED -> {
if (item.color != null) setTextColor(item.color)
}
TEXT_AND_COLOR_CHANGED -> {
if (item.color != null) setTextColor(item.color)
if (content.text.toString() != item.text) content.setText(item.text)
}
FOCUS_AND_COLOR_CHANGED -> {
if (item.color != null) setTextColor(item.color)
}
}
}
}
class Title(view: View) : BlockViewHolder(view), TextHolder {
@ -344,7 +321,7 @@ sealed class BlockViewHolder(view: View) : RecyclerView.ViewHolder(view) {
onFocusChanged: (String, Boolean) -> Unit
) {
checkbox.isSelected = item.checked
checkbox.isSelected = item.isChecked
checkbox.setOnClickListener {
checkbox.isSelected = !checkbox.isSelected
@ -379,27 +356,6 @@ sealed class BlockViewHolder(view: View) : RecyclerView.ViewHolder(view) {
content.selectionDetector = { onSelectionChanged(item.id, it) }
}
fun processChangePayload(
payloads: List<Any>,
item: BlockView.Checkbox
) = payloads.forEach { payload ->
when (payload) {
MARKUP_CHANGED -> setMarkup(markup = item)
TEXT_CHANGED -> {
if (content.text.toString() != item.text)
content.setText(item.text)
}
TEXT_AND_MARKUP_CHANGED -> {
if (content.text.toString() != item.text)
content.setText(item.text)
setMarkup(markup = item)
}
FOCUS_CHANGED -> {
setFocus(item)
}
}
}
}
class Task(view: View) : BlockViewHolder(view) {
@ -473,45 +429,6 @@ sealed class BlockViewHolder(view: View) : RecyclerView.ViewHolder(view) {
super.setTextColor(color)
bullet.tint(content.context.color(R.color.black))
}
fun processChangePayload(
payloads: List<Any>,
item: BlockView.Bulleted
) = payloads.forEach { payload ->
Timber.d("Applying change payload: $payload")
when (payload) {
MARKUP_CHANGED -> setMarkup(markup = item)
TEXT_CHANGED -> {
if (content.text.toString() != item.text)
content.setText(item.text)
}
TEXT_AND_MARKUP_CHANGED -> {
if (content.text.toString() != item.text)
content.setText(item.text)
setMarkup(markup = item)
}
TEXT_MARKUP_AND_COLOR_CHANGED -> {
if (item.color != null) setTextColor(item.color)
if (content.text.toString() != item.text) content.setText(item.text)
setMarkup(markup = item)
}
FOCUS_CHANGED -> {
setFocus(item)
}
FOCUS_AND_COLOR_CHANGED -> {
if (item.color != null) setTextColor(item.color)
}
TEXT_COLOR_CHANGED -> {
if (item.color != null) setTextColor(item.color)
}
TEXT_AND_COLOR_CHANGED -> {
if (item.color != null) setTextColor(item.color)
if (content.text.toString() != item.text) content.setText(item.text)
}
}
}
}
class Numbered(view: View) : BlockViewHolder(view) {

View file

@ -3,8 +3,12 @@ package com.agileburo.anytype.core_ui
import com.agileburo.anytype.core_ui.common.Markup
import com.agileburo.anytype.core_ui.features.page.BlockView
import com.agileburo.anytype.core_ui.features.page.BlockViewDiffUtil
import com.agileburo.anytype.core_ui.features.page.BlockViewDiffUtil.Companion.MARKUP_CHANGED
import com.agileburo.anytype.core_ui.features.page.BlockViewDiffUtil.Companion.TEXT_CHANGED
import com.agileburo.anytype.core_ui.features.page.BlockViewDiffUtil.Payload
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull
class BlockViewDiffUtilTest {
@ -121,7 +125,7 @@ class BlockViewDiffUtilTest {
assertEquals(
actual = payload,
expected = BlockViewDiffUtil.MARKUP_CHANGED
expected = Payload(listOf(MARKUP_CHANGED))
)
}
@ -162,7 +166,7 @@ class BlockViewDiffUtilTest {
assertEquals(
actual = payload,
expected = BlockViewDiffUtil.TEXT_CHANGED
expected = Payload(listOf(TEXT_CHANGED))
)
}
@ -201,7 +205,37 @@ class BlockViewDiffUtilTest {
assertEquals(
actual = payload,
expected = BlockViewDiffUtil.TEXT_AND_MARKUP_CHANGED
expected = Payload(listOf(TEXT_CHANGED, MARKUP_CHANGED))
)
}
@Test
fun `should return empty payload if types differ`() {
val index = 0
val id = MockDataFactory.randomUuid()
val text = MockDataFactory.randomString()
val oldBlock: BlockView = BlockView.HeaderOne(
id = id,
text = text
)
val newBlock: BlockView = BlockView.HeaderOne(
id = id,
text = text
)
val old = listOf(oldBlock)
val new = listOf(newBlock)
val diff = BlockViewDiffUtil(old = old, new = new)
val payload = diff.getChangePayload(index, index)
assertNull(actual = payload)
}
}

View file

@ -34,5 +34,15 @@ inline fun <reified T> MutableList<T>.shiftDown(srcIndex: Int, dstIndex: Int) =
}
}
inline fun <reified T> List<*>.typeOf(): List<T> {
val retlist = mutableListOf<T>()
this.forEach {
if (it is T) {
retlist.add(it)
}
}
return retlist
}
fun Context.toast(msg: CharSequence) = Toast.makeText(this, msg, Toast.LENGTH_LONG).show()
fun Fragment.toast(msg: CharSequence) = requireActivity().toast(msg)

View file

@ -62,7 +62,7 @@ fun Block.toView(focused: Boolean = false): BlockView = when (val content = this
id = id,
text = content.text,
marks = mapMarks(content),
checked = content.isChecked == true,
isChecked = content.isChecked == true,
focused = focused
)
Style.TOGGLE -> BlockView.Toggle(