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

Editor | Feature | Document title and todo title, color and background support (#2264)

* title margins

* block title background + tests

* added color and background color params to title blocks

* map color and background color to views

* update diff util for title blocks

* todo title design fix

* title views, set color and background for document and todo blocks

* added tests on text changed payload

Co-authored-by: konstantiniiv <ki@anytype.io>
This commit is contained in:
Konstantin Ivanov 2022-05-16 16:50:49 +03:00 committed by GitHub
parent 1c16530b11
commit 08bbf7368c
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 319 additions and 20 deletions

View file

@ -78,6 +78,7 @@ dependencies {
testImplementation acceptanceTesting.fragmentTesting
testImplementation project(':test:android-utils')
testImplementation project(':test:utils')
testImplementation project(":test:core-models-stub")
testImplementation unitTestDependencies.junit
testImplementation unitTestDependencies.kotlinTest
testImplementation unitTestDependencies.robolectricLatest

View file

@ -37,8 +37,6 @@ class BlockViewDiffUtil(
val changes = mutableListOf<Int>()
if (newBlock is BlockView.Title.Basic && oldBlock is BlockView.Title.Basic) {
if (newBlock.text != oldBlock.text)
changes.add(TEXT_CHANGED)
if (newBlock.emoji != oldBlock.emoji || newBlock.image != oldBlock.image)
changes.add(TITLE_ICON_CHANGED)
if (newBlock.coverColor != oldBlock.coverColor
@ -50,8 +48,6 @@ class BlockViewDiffUtil(
}
if (newBlock is BlockView.Title.Todo && oldBlock is BlockView.Title.Todo) {
if (newBlock.text != oldBlock.text)
changes.add(TEXT_CHANGED)
if (newBlock.coverColor != oldBlock.coverColor
|| newBlock.coverGradient != oldBlock.coverGradient
|| newBlock.coverImage != oldBlock.coverImage
@ -64,8 +60,6 @@ class BlockViewDiffUtil(
}
if (newBlock is BlockView.Title.Profile && oldBlock is BlockView.Title.Profile) {
if (newBlock.text != oldBlock.text)
changes.add(TEXT_CHANGED)
if (newBlock.image != oldBlock.image)
changes.add(TITLE_ICON_CHANGED)
if (newBlock.coverColor != oldBlock.coverColor

View file

@ -18,6 +18,7 @@ import com.anytypeio.anytype.core_ui.common.SearchTargetHighlightSpan
import com.anytypeio.anytype.core_ui.databinding.ItemBlockTitleBinding
import com.anytypeio.anytype.core_ui.databinding.ItemBlockTitleProfileBinding
import com.anytypeio.anytype.core_ui.databinding.ItemBlockTitleTodoBinding
import com.anytypeio.anytype.core_ui.extensions.setBlockBackgroundColor
import com.anytypeio.anytype.core_ui.features.editor.BlockViewDiffUtil
import com.anytypeio.anytype.core_ui.features.editor.BlockViewHolder
import com.anytypeio.anytype.core_ui.features.editor.holders.`interface`.TextHolder
@ -28,6 +29,7 @@ import com.anytypeio.anytype.emojifier.Emojifier
import com.anytypeio.anytype.presentation.editor.cover.CoverColor
import com.anytypeio.anytype.presentation.editor.cover.CoverGradient
import com.anytypeio.anytype.presentation.editor.editor.KeyPressedEvent
import com.anytypeio.anytype.presentation.editor.editor.ThemeColor
import com.anytypeio.anytype.presentation.editor.editor.listener.ListenerType
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
import com.bumptech.glide.Glide
@ -48,6 +50,8 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder {
onCoverClicked: () -> Unit
) {
setImage(item)
applyTextColor(item)
applyBackground(item)
setCover(
coverColor = item.coverColor,
coverImage = item.coverImage,
@ -222,6 +226,12 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder {
coverGradient = item.coverGradient
)
}
if (payload.isBackgroundColorChanged) {
applyBackground(item)
}
if (payload.isTextColorChanged) {
applyTextColor(item)
}
}
}
@ -254,6 +264,8 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder {
}
override fun select(item: BlockView.Selectable) = Unit
abstract fun applyTextColor(item: BlockView.Title)
abstract fun applyBackground(item: BlockView.Title)
class Document(val binding: ItemBlockTitleBinding) : Title(binding.root) {
@ -358,6 +370,14 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder {
Timber.e(e, "Could not set emoji icon")
}
}
override fun applyTextColor(item: BlockView.Title) {
setTextColor(item.color ?: ThemeColor.DEFAULT.title)
}
override fun applyBackground(item: BlockView.Title) {
content.setBlockBackgroundColor(item.backgroundColor)
}
}
class Archive(val binding: ItemBlockTitleBinding) : Title(binding.root) {
@ -389,6 +409,9 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder {
.load(R.drawable.ic_bin_big)
.into(image)
}
override fun applyTextColor(item: BlockView.Title) {}
override fun applyBackground(item: BlockView.Title) {}
}
class Profile(val binding: ItemBlockTitleProfileBinding) : Title(binding.root) {
@ -469,6 +492,9 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder {
}
}
}
override fun applyTextColor(item: BlockView.Title) {}
override fun applyBackground(item: BlockView.Title) {}
}
class Todo(val binding: ItemBlockTitleTodoBinding) : Title(binding.root) {
@ -528,5 +554,12 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder {
}
}
}
override fun applyTextColor(item: BlockView.Title) {
setTextColor(item.color ?: ThemeColor.DEFAULT.title)
}
override fun applyBackground(item: BlockView.Title) {
binding.titleContainer.setBlockBackgroundColor(item.backgroundColor)
}
}
}

View file

@ -2,6 +2,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
@ -85,9 +86,9 @@
<com.anytypeio.anytype.core_ui.widgets.text.TextInputWidget
android:id="@+id/title"
style="@style/BlockTitleContentStyle"
android:layout_marginStart="20dp"
android:paddingStart="20dp"
android:layout_marginTop="0dp"
android:layout_marginEnd="20dp"
android:paddingEnd="20dp"
android:hint="@string/untitled"
android:paddingTop="0dp"
android:layout_marginBottom="6dp"

View file

@ -52,9 +52,9 @@
android:id="@+id/titleContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:paddingStart="20dp"
android:layout_marginTop="@dimen/space_between_header_container_and_title"
android:layout_marginEnd="20dp"
android:paddingEnd="20dp"
android:orientation="horizontal">
<View

View file

@ -1382,4 +1382,132 @@ class BlockViewDiffUtilTest {
actual = payload
)
}
@Test
fun `should return text change payload for title basic`() {
val index = 0
val id = MockDataFactory.randomUuid()
val newText = MockDataFactory.randomString()
val oldBlock: BlockView = BlockView.Title.Basic(
id = id,
text = MockDataFactory.randomString()
)
val newBlock: BlockView = BlockView.Title.Basic(
id = id,
text = newText
)
val old = listOf(oldBlock)
val new = listOf(newBlock)
val diff = BlockViewDiffUtil(old = old, new = new)
val payload = diff.getChangePayload(index, index)
assertEquals(
actual = payload,
expected = Payload(listOf(TEXT_CHANGED))
)
}
@Test
fun `should return text change payload for title todo`() {
val index = 0
val id = MockDataFactory.randomUuid()
val newText = MockDataFactory.randomString()
val oldBlock: BlockView = BlockView.Title.Todo(
id = id,
text = MockDataFactory.randomString()
)
val newBlock: BlockView = BlockView.Title.Todo(
id = id,
text = newText
)
val old = listOf(oldBlock)
val new = listOf(newBlock)
val diff = BlockViewDiffUtil(old = old, new = new)
val payload = diff.getChangePayload(index, index)
assertEquals(
actual = payload,
expected = Payload(listOf(TEXT_CHANGED))
)
}
@Test
fun `should return text change payload for title profile`() {
val index = 0
val id = MockDataFactory.randomUuid()
val newText = MockDataFactory.randomString()
val oldBlock: BlockView = BlockView.Title.Profile(
id = id,
text = MockDataFactory.randomString()
)
val newBlock: BlockView = BlockView.Title.Profile(
id = id,
text = newText
)
val old = listOf(oldBlock)
val new = listOf(newBlock)
val diff = BlockViewDiffUtil(old = old, new = new)
val payload = diff.getChangePayload(index, index)
assertEquals(
actual = payload,
expected = Payload(listOf(TEXT_CHANGED))
)
}
@Test
fun `should return text change payload for title archive`() {
val index = 0
val id = MockDataFactory.randomUuid()
val newText = MockDataFactory.randomString()
val oldBlock: BlockView = BlockView.Title.Archive(
id = id,
text = MockDataFactory.randomString()
)
val newBlock: BlockView = BlockView.Title.Archive(
id = id,
text = newText
)
val old = listOf(oldBlock)
val new = listOf(newBlock)
val diff = BlockViewDiffUtil(old = old, new = new)
val payload = diff.getChangePayload(index, index)
assertEquals(
actual = payload,
expected = Payload(listOf(TEXT_CHANGED))
)
}
}

View file

@ -0,0 +1,123 @@
package com.anytypeio.anytype.core_ui.uitests
import android.content.Context
import android.content.res.Resources
import android.os.Build
import androidx.fragment.app.Fragment
import androidx.fragment.app.testing.FragmentScenario
import androidx.fragment.app.testing.launchFragmentInContainer
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.test.core.app.ApplicationProvider
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
import com.anytypeio.anytype.test_utils.TestFragment
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.extensions.lighter
import com.anytypeio.anytype.presentation.editor.editor.ThemeColor
import com.anytypeio.anytype.test_utils.MockDataFactory
import com.anytypeio.anytype.test_utils.utils.checkHasBackgroundColor
import com.anytypeio.anytype.test_utils.utils.checkHasNoBackground
import com.anytypeio.anytype.test_utils.utils.checkHasText
import com.anytypeio.anytype.test_utils.utils.checkIsDisplayed
import com.anytypeio.anytype.test_utils.utils.onItemView
import com.anytypeio.anytype.test_utils.utils.rVMatcher
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import com.anytypeio.anytype.test_utils.R as TestResource
@RunWith(RobolectricTestRunner::class)
@Config(
manifest = Config.NONE,
sdk = [Build.VERSION_CODES.P],
instrumentedPackages = ["androidx.loader.content"]
)
class TitleBlockTest {
private val context: Context = ApplicationProvider.getApplicationContext()
private val resources: Resources = context.resources
private lateinit var scenario: FragmentScenario<TestFragment>
@Before
fun setUp() {
context.setTheme(R.style.Theme_MaterialComponents)
scenario = launchFragmentInContainer()
}
@Test
fun `should show title block with default background`() {
scenario.onFragment {
// SETUP
val title = givenTitleBlock()
val recycler = givenRecycler(it)
val adapter = givenAdapter(listOf(title))
recycler.adapter = adapter
val rvMatcher = TestResource.id.recycler.rVMatcher()
// TESTING
rvMatcher.apply {
onItemView(0, R.id.root).checkIsDisplayed()
onItemView(0, R.id.title).checkHasNoBackground()
onItemView(0, R.id.title).checkHasText(title.text!!)
}
}
}
@Test
fun `should show title block with red background`() {
scenario.onFragment {
// SETUP
val redBackground = ThemeColor.RED
val title = givenTitleBlock(
backgroundColor = redBackground.title
)
val recycler = givenRecycler(it)
val adapter = givenAdapter(
listOf(title)
)
recycler.adapter = adapter
val rvMatcher = TestResource.id.recycler.rVMatcher()
// TESTING
rvMatcher.apply {
onItemView(0, R.id.root).checkIsDisplayed()
onItemView(0, R.id.title).checkHasBackgroundColor(
resources.lighter(redBackground, 0)
)
onItemView(0, R.id.title).checkHasText(title.text!!)
}
}
}
private fun givenTitleBlock(
isFocused: Boolean = false,
mode: BlockView.Mode = BlockView.Mode.EDIT,
backgroundColor: String = ThemeColor.DEFAULT.title
) = BlockView.Title.Basic(
text = MockDataFactory.randomString(),
id = MockDataFactory.randomUuid(),
mode = mode,
isFocused = isFocused,
backgroundColor = backgroundColor
)
private fun givenRecycler(fr: Fragment): RecyclerView {
val root = checkNotNull(fr.view)
return root.findViewById<RecyclerView>(TestResource.id.recycler).apply {
layoutManager = LinearLayoutManager(context)
}
}
}

View file

@ -477,13 +477,15 @@ sealed class BlockView : ViewType {
override fun getViewType(): Int = HOLDER_DESCRIPTION
}
sealed class Title : BlockView(), Focusable, Cursor, Permission {
sealed class Title : BlockView(), TextSupport, Focusable, Cursor, Permission {
abstract val image: String?
abstract var text: String?
abstract override var text: String
abstract var coverColor: CoverColor?
abstract var coverImage: Url?
abstract var coverGradient: String?
abstract override val color: String?
abstract override val backgroundColor: String?
val hasCover get() = coverColor != null || coverImage != null || coverGradient != null
@ -495,10 +497,12 @@ sealed class BlockView : ViewType {
data class Basic(
override val id: String,
override var isFocused: Boolean = false,
override var text: String? = null,
override var text: String,
override var coverColor: CoverColor? = null,
override var coverImage: Url? = null,
override var coverGradient: String? = null,
override val backgroundColor: String? = null,
override val color: String? = null,
val emoji: String? = null,
override val image: String? = null,
override val mode: Mode = Mode.EDIT,
@ -517,10 +521,12 @@ sealed class BlockView : ViewType {
data class Profile(
override val id: String,
override var isFocused: Boolean = false,
override var text: String? = null,
override var text: String,
override var coverColor: CoverColor? = null,
override var coverImage: Url? = null,
override var coverGradient: String? = null,
override val backgroundColor: String? = null,
override val color: String? = null,
override val image: String? = null,
override val mode: Mode = Mode.EDIT,
override var cursor: Int? = null,
@ -537,11 +543,13 @@ sealed class BlockView : ViewType {
data class Todo(
override val id: String,
override var isFocused: Boolean = false,
override var text: String? = null,
override var text: String,
override val image: String? = null,
override var coverColor: CoverColor? = null,
override var coverImage: Url? = null,
override var coverGradient: String? = null,
override val backgroundColor: String? = null,
override val color: String? = null,
override val mode: Mode = Mode.EDIT,
override var cursor: Int? = null,
override val searchFields: List<Searchable.Field> = emptyList(),
@ -559,11 +567,13 @@ sealed class BlockView : ViewType {
data class Archive(
override val id: String,
override var isFocused: Boolean = false,
override var text: String?,
override var text: String,
override val image: String? = null,
override var coverColor: CoverColor? = null,
override var coverImage: Url? = null,
override var coverGradient: String? = null,
override val backgroundColor: String? = null,
override val color: String? = null,
override val mode: Mode = Mode.READ,
override var cursor: Int? = null
) : Title() {

View file

@ -10,6 +10,7 @@ import com.anytypeio.anytype.core_models.Relation
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.core_models.SmartBlockType
import com.anytypeio.anytype.core_models.Url
import com.anytypeio.anytype.core_models.ext.textColor
import com.anytypeio.anytype.core_models.restrictions.ObjectRestriction
import com.anytypeio.anytype.domain.editor.Editor.Cursor
import com.anytypeio.anytype.domain.editor.Editor.Focus
@ -69,7 +70,7 @@ class DefaultBlockViewRenderer @Inject constructor(
BlockView.Title.Archive(
mode = BlockView.Mode.READ,
id = anchor,
text = details.details[root.id]?.name
text = details.details[root.id]?.name.orEmpty()
)
)
}
@ -1187,6 +1188,8 @@ class DefaultBlockViewRenderer @Inject constructor(
coverColor = coverColor,
coverImage = coverImage,
coverGradient = coverGradient,
backgroundColor = block.backgroundColor,
color = block.textColor()
)
}
ObjectType.Layout.TODO -> {
@ -1199,7 +1202,9 @@ class DefaultBlockViewRenderer @Inject constructor(
coverColor = coverColor,
coverImage = coverImage,
coverGradient = coverGradient,
isChecked = content.isChecked == true
isChecked = content.isChecked == true,
backgroundColor = block.backgroundColor,
color = block.textColor()
)
}
ObjectType.Layout.PROFILE -> {
@ -1217,7 +1222,9 @@ class DefaultBlockViewRenderer @Inject constructor(
cursor = cursor,
coverColor = coverColor,
coverImage = coverImage,
coverGradient = coverGradient
coverGradient = coverGradient,
backgroundColor = block.backgroundColor,
color = block.textColor()
)
}
ObjectType.Layout.FILE, ObjectType.Layout.IMAGE -> {
@ -1242,7 +1249,9 @@ class DefaultBlockViewRenderer @Inject constructor(
cursor = cursor,
coverColor = coverColor,
coverImage = coverImage,
coverGradient = coverGradient
coverGradient = coverGradient,
backgroundColor = block.backgroundColor,
color = block.textColor()
)
}
else -> throw IllegalStateException("Unexpected layout: $layout")