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

Relations | Enhancement | Do not allow editing relations when object is locked (#2258)

This commit is contained in:
Evgenii Kozlov 2022-05-16 17:18:03 +03:00 committed by GitHub
parent bdf231af5d
commit c0bbae162a
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 585 additions and 158 deletions

View file

@ -176,6 +176,7 @@ class DisplayRelationObjectValueTest {
launchFragment(
bundleOf(
RelationValueBaseFragment.CTX_KEY to root,
RelationValueBaseFragment.IS_LOCKED_KEY to false,
RelationValueBaseFragment.RELATION_KEY to relation,
RelationValueBaseFragment.TARGET_KEY to target
)
@ -253,7 +254,8 @@ class DisplayRelationObjectValueTest {
bundleOf(
RelationValueBaseFragment.CTX_KEY to root,
RelationValueBaseFragment.RELATION_KEY to relation,
RelationValueBaseFragment.TARGET_KEY to target
RelationValueBaseFragment.TARGET_KEY to target,
RelationValueBaseFragment.IS_LOCKED_KEY to false,
)
)
@ -324,7 +326,8 @@ class DisplayRelationObjectValueTest {
bundleOf(
RelationValueBaseFragment.CTX_KEY to root,
RelationValueBaseFragment.RELATION_KEY to relationId,
RelationValueBaseFragment.TARGET_KEY to targetId
RelationValueBaseFragment.TARGET_KEY to targetId,
RelationValueBaseFragment.IS_LOCKED_KEY to false,
)
)
@ -442,7 +445,8 @@ class DisplayRelationObjectValueTest {
bundleOf(
RelationValueBaseFragment.CTX_KEY to root,
RelationValueBaseFragment.RELATION_KEY to relationId,
RelationValueBaseFragment.TARGET_KEY to recordId
RelationValueBaseFragment.TARGET_KEY to recordId,
RelationValueBaseFragment.IS_LOCKED_KEY to false,
)
)
@ -572,7 +576,8 @@ class DisplayRelationObjectValueTest {
bundleOf(
RelationValueBaseFragment.CTX_KEY to root,
RelationValueBaseFragment.RELATION_KEY to relationId,
RelationValueBaseFragment.TARGET_KEY to recordId
RelationValueBaseFragment.TARGET_KEY to recordId,
RelationValueBaseFragment.IS_LOCKED_KEY to false,
)
)
@ -690,7 +695,8 @@ class DisplayRelationObjectValueTest {
bundleOf(
RelationValueBaseFragment.CTX_KEY to root,
RelationValueBaseFragment.RELATION_KEY to relationId,
RelationValueBaseFragment.TARGET_KEY to recordId
RelationValueBaseFragment.TARGET_KEY to recordId,
RelationValueBaseFragment.IS_LOCKED_KEY to false,
)
)

View file

@ -205,7 +205,8 @@ class EditRelationTagValueTest {
bundleOf(
RelationValueBaseFragment.CTX_KEY to ctx,
RelationValueBaseFragment.RELATION_KEY to relationKey,
RelationValueBaseFragment.TARGET_KEY to target
RelationValueBaseFragment.TARGET_KEY to target,
RelationValueBaseFragment.IS_LOCKED_KEY to false,
)
)
@ -326,7 +327,8 @@ class EditRelationTagValueTest {
RelationValueBaseFragment.DATAVIEW_KEY to dv.id,
RelationValueBaseFragment.VIEWER_KEY to viewer.id,
RelationValueBaseFragment.RELATION_KEY to relationKey,
RelationValueBaseFragment.TARGET_KEY to target
RelationValueBaseFragment.TARGET_KEY to target,
RelationValueBaseFragment.IS_LOCKED_KEY to false,
)
)
@ -350,7 +352,7 @@ class EditRelationTagValueTest {
}
private fun launchFragment(args: Bundle): FragmentScenario<TestRelationValueDVFragment> {
return launchFragmentInContainer<TestRelationValueDVFragment>(
return launchFragmentInContainer(
fragmentArgs = args,
themeResId = R.style.AppTheme
)

View file

@ -935,6 +935,7 @@ open class EditorFragment : NavigationFragment<FragmentEditorBinding>(R.layout.f
ObjectMenuBaseFragment.CTX_KEY to ctx,
ObjectMenuBaseFragment.IS_ARCHIVED_KEY to command.isArchived,
ObjectMenuBaseFragment.IS_FAVORITE_KEY to command.isFavorite,
ObjectMenuBaseFragment.IS_LOCKED_KEY to command.isLocked,
ObjectMenuBaseFragment.IS_PROFILE_KEY to false
)
)
@ -947,6 +948,7 @@ open class EditorFragment : NavigationFragment<FragmentEditorBinding>(R.layout.f
ObjectMenuBaseFragment.CTX_KEY to ctx,
ObjectMenuBaseFragment.IS_ARCHIVED_KEY to false,
ObjectMenuBaseFragment.IS_FAVORITE_KEY to command.isFavorite,
ObjectMenuBaseFragment.IS_LOCKED_KEY to command.isLocked,
ObjectMenuBaseFragment.IS_PROFILE_KEY to true
)
)
@ -1010,7 +1012,8 @@ open class EditorFragment : NavigationFragment<FragmentEditorBinding>(R.layout.f
bundleOf(
RelationListFragment.ARG_CTX to command.ctx,
RelationListFragment.ARG_TARGET to command.target,
RelationListFragment.ARG_MODE to RelationListFragment.MODE_LIST
RelationListFragment.ARG_LOCKED to command.isLocked,
RelationListFragment.ARG_MODE to RelationListFragment.MODE_LIST,
)
)
}
@ -1029,7 +1032,8 @@ open class EditorFragment : NavigationFragment<FragmentEditorBinding>(R.layout.f
val fr = RelationTextValueFragment.new(
ctx = command.ctx,
objectId = command.target,
relationId = command.relation
relationId = command.relation,
isLocked = command.isLocked
)
fr.show(childFragmentManager, null)
}

View file

@ -37,6 +37,7 @@ abstract class ObjectMenuBaseFragment : BaseBottomSheetFragment<FragmentObjectMe
private val isProfile get() = arg<Boolean>(IS_PROFILE_KEY)
private val isArchived get() = arg<Boolean>(IS_ARCHIVED_KEY)
private val isFavorite get() = arg<Boolean>(IS_FAVORITE_KEY)
private val isLocked get() = arg<Boolean>(IS_LOCKED_KEY)
abstract val vm: ObjectMenuViewModelBase
@ -130,6 +131,7 @@ abstract class ObjectMenuBaseFragment : BaseBottomSheetFragment<FragmentObjectMe
bundleOf(
RelationListFragment.ARG_CTX to ctx,
RelationListFragment.ARG_TARGET to null,
RelationListFragment.ARG_LOCKED to isLocked,
RelationListFragment.ARG_MODE to RelationListFragment.MODE_LIST
)
)
@ -170,6 +172,7 @@ abstract class ObjectMenuBaseFragment : BaseBottomSheetFragment<FragmentObjectMe
const val IS_ARCHIVED_KEY = "arg.doc-menu-bottom-sheet.is-archived"
const val IS_PROFILE_KEY = "arg.doc-menu-bottom-sheet.is-profile"
const val IS_FAVORITE_KEY = "arg.doc-menu-bottom-sheet.is-favorite"
const val IS_LOCKED_KEY = "arg.doc-menu-bottom-sheet.is-locked"
const val COMING_SOON_MSG = "Coming soon..."
}
@ -207,20 +210,4 @@ class ObjectMenuFragment : ObjectMenuBaseFragment() {
override fun releaseDependencies() {
componentManager().objectMenuComponent.release(ctx)
}
companion object {
fun new(
ctx: Id,
isProfile: Boolean = false,
isArchived: Boolean,
isFavorite: Boolean
) = ObjectMenuFragment().apply {
arguments = bundleOf(
CTX_KEY to ctx,
IS_ARCHIVED_KEY to isArchived,
IS_PROFILE_KEY to isProfile,
IS_FAVORITE_KEY to isFavorite
)
}
}
}

View file

@ -42,6 +42,7 @@ open class RelationListFragment : BaseBottomSheetFragment<FragmentRelationListBi
private val ctx: String get() = argString(ARG_CTX)
private val target: String? get() = argStringOrNull(ARG_TARGET)
private val mode: Int get() = argInt(ARG_MODE)
private val isLocked: Boolean get() = arg(ARG_LOCKED)
private val docRelationAdapter by lazy {
DocumentRelationAdapter(
@ -89,11 +90,13 @@ open class RelationListFragment : BaseBottomSheetFragment<FragmentRelationListBi
)
}
binding.btnPlus.setOnClickListener {
RelationAddToObjectFragment.new(ctx).show(childFragmentManager, null)
}
binding.btnEditOrDone.setOnClickListener {
vm.onEditOrDoneClicked()
if (!isLocked) {
RelationAddToObjectFragment.new(ctx).show(childFragmentManager, null)
} else {
toast(getString(R.string.unlock_your_object_to_add_new_relation))
}
}
binding.btnEditOrDone.setOnClickListener { vm.onEditOrDoneClicked(isLocked) }
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
@ -144,7 +147,8 @@ open class RelationListFragment : BaseBottomSheetFragment<FragmentRelationListBi
val fr = RelationTextValueFragment.new(
ctx = ctx,
relationId = command.relation,
objectId = command.target
objectId = command.target,
isLocked = command.isLocked
)
fr.show(childFragmentManager, null)
}
@ -163,7 +167,8 @@ open class RelationListFragment : BaseBottomSheetFragment<FragmentRelationListBi
RelationValueBaseFragment.CTX_KEY to command.ctx,
RelationValueBaseFragment.TARGET_KEY to command.target,
RelationValueBaseFragment.RELATION_KEY to command.relation,
RelationValueBaseFragment.TARGET_TYPES_KEY to command.targetObjectTypes
RelationValueBaseFragment.TARGET_TYPES_KEY to command.targetObjectTypes,
RelationValueBaseFragment.IS_LOCKED_KEY to command.isLocked
)
)
}
@ -238,17 +243,24 @@ open class RelationListFragment : BaseBottomSheetFragment<FragmentRelationListBi
)
companion object {
fun new(ctx: String, target: String?, mode: Int) = RelationListFragment().apply {
fun new(
ctx: String,
target: String?,
mode: Int,
locked: Boolean = false
) = RelationListFragment().apply {
arguments = bundleOf(
ARG_CTX to ctx,
ARG_TARGET to target,
ARG_MODE to mode
ARG_MODE to mode,
ARG_LOCKED to locked
)
}
const val ARG_CTX = "arg.document-relation.ctx"
const val ARG_MODE = "arg.document-relation.mode"
const val ARG_TARGET = "arg.document-relation.target"
const val ARG_LOCKED = "arg.document-relation.locked"
const val MODE_ADD = 1
const val MODE_LIST = 2
}

View file

@ -13,7 +13,12 @@ import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_ui.features.relations.RelationTextValueAdapter
import com.anytypeio.anytype.core_utils.ext.*
import com.anytypeio.anytype.core_utils.ext.arg
import com.anytypeio.anytype.core_utils.ext.hideKeyboard
import com.anytypeio.anytype.core_utils.ext.normalizeUrl
import com.anytypeio.anytype.core_utils.ext.subscribe
import com.anytypeio.anytype.core_utils.ext.toast
import com.anytypeio.anytype.core_utils.ext.withParent
import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetImeOffsetFragment
import com.anytypeio.anytype.databinding.FragmentRelationTextValueBinding
import com.anytypeio.anytype.di.common.componentManager
@ -36,6 +41,7 @@ open class RelationTextValueFragment :
private val relationId get() = arg<String>(RELATION_ID)
private val objectId get() = arg<String>(OBJECT_ID)
private val flow get() = arg<Int>(FLOW_KEY)
private val isLocked get() = arg<Boolean>(LOCKED_KEY)
private val relationValueAdapter by lazy {
RelationTextValueAdapter(
@ -72,7 +78,11 @@ open class RelationTextValueFragment :
jobs += lifecycleScope.subscribe(vm.views) { relationValueAdapter.update(it) }
jobs += lifecycleScope.subscribe(vm.title) { binding.tvRelationHeader.text = it }
super.onStart()
vm.onStart(relationId = relationId, recordId = objectId)
vm.onStart(
relationId = relationId,
recordId = objectId,
isLocked = isLocked
)
}
override fun onStop() {
@ -195,13 +205,15 @@ open class RelationTextValueFragment :
ctx: Id,
relationId: Id,
objectId: Id,
flow: Int = FLOW_DEFAULT
flow: Int = FLOW_DEFAULT,
isLocked: Boolean = false
) = RelationTextValueFragment().apply {
arguments = bundleOf(
CONTEXT_ID to ctx,
RELATION_ID to relationId,
OBJECT_ID to objectId,
FLOW_KEY to flow
FLOW_KEY to flow,
LOCKED_KEY to isLocked
)
}
@ -209,6 +221,7 @@ open class RelationTextValueFragment :
const val RELATION_ID = "arg.edit-relation-value.relation.id"
const val OBJECT_ID = "arg.edit-relation-value.object.id"
const val FLOW_KEY = "arg.edit-relation-value.flow"
const val LOCKED_KEY = "arg.edit-relation-value.locked"
const val FLOW_DEFAULT = 0
const val FLOW_DATAVIEW = 1

View file

@ -78,6 +78,7 @@ abstract class RelationValueBaseFragment : BaseBottomSheetFragment<FragmentRelat
protected val dataview get() = argString(DATAVIEW_KEY)
protected val viewer get() = argString(VIEWER_KEY)
protected val types get() = arg<List<String>>(TARGET_TYPES_KEY)
protected val isLocked get() = arg<Boolean>(IS_LOCKED_KEY)
abstract val vm: RelationValueBaseViewModel
@ -143,15 +144,15 @@ abstract class RelationValueBaseFragment : BaseBottomSheetFragment<FragmentRelat
setDrawable(drawable(R.drawable.divider_relations_edit))
}
with(lifecycleScope) {
subscribe(binding.btnEditOrDone.clicks()) { vm.onEditOrDoneClicked() }
subscribe(binding.btnAddValue.clicks()) { vm.onAddValueClicked() }
subscribe(binding.btnEditOrDone.clicks()) { vm.onEditOrDoneClicked(isLocked) }
subscribe(binding.btnAddValue.clicks()) { vm.onAddValueClicked(isLocked) }
}
}
override fun onStart() {
jobs += lifecycleScope.subscribe(vm.toasts) { toast(it) }
jobs += lifecycleScope.subscribe(vm.commands) { observeCommands(it) }
jobs += lifecycleScope.subscribe(vm.isDimissed) { observeDismiss(it) }
jobs += lifecycleScope.subscribe(vm.isDismissed) { observeDismiss(it) }
jobs += lifecycleScope.subscribe(vm.isEditing) { observeEditing(it) }
jobs += lifecycleScope.subscribe(vm.views) { relationValueAdapter.update(it) }
jobs += lifecycleScope.subscribe(vm.name) { binding.tvTagOrStatusRelationHeader.text = it }
@ -424,6 +425,7 @@ abstract class RelationValueBaseFragment : BaseBottomSheetFragment<FragmentRelat
companion object {
const val CTX_KEY = "arg.edit-cell-tag.ctx"
const val IS_LOCKED_KEY = "arg.edit-cell-tag.locked"
const val RELATION_KEY = "arg.edit-cell-tag.relation"
const val TARGET_KEY = "arg.edit-cell-tag.target"
const val DATAVIEW_KEY = "arg.edit-cell-tag.dataview"

View file

@ -510,7 +510,8 @@ open class ObjectSetFragment :
ObjectMenuBaseFragment.CTX_KEY to command.ctx,
ObjectMenuBaseFragment.IS_ARCHIVED_KEY to command.isArchived,
ObjectMenuBaseFragment.IS_FAVORITE_KEY to command.isFavorite,
ObjectMenuBaseFragment.IS_PROFILE_KEY to false
ObjectMenuBaseFragment.IS_PROFILE_KEY to false,
ObjectMenuBaseFragment.IS_LOCKED_KEY to false
)
)
}
@ -541,7 +542,8 @@ open class ObjectSetFragment :
RelationValueBaseFragment.DATAVIEW_KEY to command.dataview,
RelationValueBaseFragment.RELATION_KEY to command.relation,
RelationValueBaseFragment.VIEWER_KEY to command.viewer,
RelationValueBaseFragment.TARGET_TYPES_KEY to command.targetObjectTypes
RelationValueBaseFragment.TARGET_TYPES_KEY to command.targetObjectTypes,
RelationValueBaseFragment.IS_LOCKED_KEY to false
)
)
}

View file

@ -283,4 +283,6 @@ Do the computation of an expensive paragraph of text on a background thread:
<string name="without_template">Without template</string>
<string name="unsplash_generic_error">Error while searching for images on Unsplash. Please try again later.</string>
<string name="do_not_forget_to_save_recovery_phrase_msg">Dont forget to take and save your recovery phrase from settings</string>
<string name="unlock_your_object_to_add_new_relation">Unlock your object to add new relation</string>
<string name="unlock_your_object_to_edit_relations">Unlock your object to edit relations</string>
</resources>

View file

@ -8,7 +8,13 @@ import androidx.recyclerview.widget.RecyclerView
import com.anytypeio.anytype.core_ui.BuildConfig
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.databinding.ItemObjectRelationTextBinding
import com.anytypeio.anytype.core_ui.features.relations.holders.*
import com.anytypeio.anytype.core_ui.features.relations.holders.RelationEmailHolder
import com.anytypeio.anytype.core_ui.features.relations.holders.RelationNumberHolder
import com.anytypeio.anytype.core_ui.features.relations.holders.RelationPhoneHolder
import com.anytypeio.anytype.core_ui.features.relations.holders.RelationTextHolder
import com.anytypeio.anytype.core_ui.features.relations.holders.RelationTextShortHolder
import com.anytypeio.anytype.core_ui.features.relations.holders.RelationTextValueViewHolderBase
import com.anytypeio.anytype.core_ui.features.relations.holders.RelationUrlHolder
import com.anytypeio.anytype.core_utils.ext.syncFocusWithImeVisibility
import com.anytypeio.anytype.core_utils.text.ActionDoneListener
import com.anytypeio.anytype.presentation.sets.EditGridCellAction
@ -18,9 +24,9 @@ class RelationTextValueAdapter(
private var items: List<RelationTextValueView>,
private val actionClick: (EditGridCellAction) -> Unit,
private val onEditCompleted: (RelationTextValueView, String) -> Unit
) : RecyclerView.Adapter<RelationBaseHolder>() {
) : RecyclerView.Adapter<RelationTextValueViewHolderBase>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RelationBaseHolder {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RelationTextValueViewHolderBase {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
TYPE_TEXT -> RelationTextHolder(
@ -111,30 +117,34 @@ class RelationTextValueAdapter(
}
}
override fun onBindViewHolder(holder: RelationBaseHolder, position: Int) {
override fun onBindViewHolder(holder: RelationTextValueViewHolderBase, position: Int) {
val item = items[position]
when (holder) {
is RelationTextHolder -> holder.bind(
items[position] as RelationTextValueView.Text
item as RelationTextValueView.Text
)
is RelationTextShortHolder -> holder.bind(
items[position] as RelationTextValueView.TextShort
item as RelationTextValueView.TextShort
)
is RelationPhoneHolder -> holder.bind(
items[position] as RelationTextValueView.Phone,
item as RelationTextValueView.Phone,
actionClick
)
is RelationEmailHolder -> holder.bind(
items[position] as RelationTextValueView.Email,
item as RelationTextValueView.Email,
actionClick
)
is RelationUrlHolder -> holder.bind(
items[position] as RelationTextValueView.Url,
item as RelationTextValueView.Url,
actionClick
)
is RelationNumberHolder -> holder.bind(
items[position] as RelationTextValueView.Number
item as RelationTextValueView.Number
)
}
if (!item.isEditable) {
holder.enableReadMode()
}
}
override fun getItemCount(): Int = items.size

View file

@ -1,6 +0,0 @@
package com.anytypeio.anytype.core_ui.features.relations.holders
import android.view.View
import androidx.recyclerview.widget.RecyclerView
open class RelationBaseHolder(view: View) : RecyclerView.ViewHolder(view)

View file

@ -1,7 +1,17 @@
package com.anytypeio.anytype.core_ui.features.relations.holders
import android.text.InputType.*
import android.text.InputType.TYPE_CLASS_NUMBER
import android.text.InputType.TYPE_CLASS_PHONE
import android.text.InputType.TYPE_CLASS_TEXT
import android.text.InputType.TYPE_NUMBER_FLAG_DECIMAL
import android.text.InputType.TYPE_NUMBER_FLAG_SIGNED
import android.text.InputType.TYPE_TEXT_FLAG_AUTO_CORRECT
import android.text.InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
import android.text.InputType.TYPE_TEXT_FLAG_MULTI_LINE
import android.text.InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
import android.text.InputType.TYPE_TEXT_VARIATION_URI
import android.view.inputmethod.EditorInfo
import android.widget.TextView
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.databinding.ItemObjectRelationTextBinding
import com.anytypeio.anytype.core_utils.ext.focusAndShowKeyboard
@ -12,7 +22,7 @@ import com.anytypeio.anytype.presentation.sets.RelationTextValueView
class RelationTextHolder(
val binding: ItemObjectRelationTextBinding
) : RelationBaseHolder(binding.root) {
) : RelationTextValueViewHolderBase(binding.root) {
init {
with(binding.textInputField) {
@ -24,9 +34,11 @@ class RelationTextHolder(
}
}
override val input: TextView get() = binding.textInputField
fun bind(view: RelationTextValueView.Text) = with(binding) {
textInputField.setText(view.value)
if (view.value.isNullOrEmpty()) {
if (view.value.isNullOrEmpty() && view.isEditable) {
textInputField.focusAndShowKeyboard()
}
btnAction.gone()
@ -35,11 +47,13 @@ class RelationTextHolder(
class RelationTextShortHolder(
val binding: ItemObjectRelationTextBinding
) : RelationBaseHolder(binding.root) {
) : RelationTextValueViewHolderBase(binding.root) {
override val input: TextView get() = binding.textInputField
fun bind(view: RelationTextValueView.TextShort) = with(binding) {
textInputField.setText(view.value)
if (view.value.isNullOrEmpty()) {
if (view.value.isNullOrEmpty() && view.isEditable) {
textInputField.focusAndShowKeyboard()
}
textInputField.inputType = TYPE_CLASS_TEXT
@ -50,14 +64,16 @@ class RelationTextShortHolder(
class RelationPhoneHolder(
val binding: ItemObjectRelationTextBinding
) : RelationBaseHolder(binding.root) {
) : RelationTextValueViewHolderBase(binding.root) {
override val input: TextView get() = binding.textInputField
fun bind(
view: RelationTextValueView.Phone,
actionClick: (EditGridCellAction) -> Unit
) = with(binding) {
textInputField.setText(view.value)
if (view.value.isNullOrEmpty()) {
if (view.value.isNullOrEmpty() && view.isEditable) {
textInputField.focusAndShowKeyboard()
} else {
ivActionIcon.setImageResource(R.drawable.ic_cell_relation_call_with)
@ -72,14 +88,16 @@ class RelationPhoneHolder(
class RelationEmailHolder(
val binding: ItemObjectRelationTextBinding
) : RelationBaseHolder(binding.root) {
) : RelationTextValueViewHolderBase(binding.root) {
override val input: TextView get() = binding.textInputField
fun bind(
view: RelationTextValueView.Email,
actionClick: (EditGridCellAction) -> Unit
) = with(binding) {
textInputField.setText(view.value)
if (view.value.isNullOrEmpty()) {
if (view.value.isNullOrEmpty() && view.isEditable) {
textInputField.focusAndShowKeyboard()
} else {
ivActionIcon.setImageResource(R.drawable.ic_cell_relation_go_to_mail_client)
@ -94,14 +112,16 @@ class RelationEmailHolder(
class RelationUrlHolder(
val binding: ItemObjectRelationTextBinding
) : RelationBaseHolder(binding.root) {
) : RelationTextValueViewHolderBase(binding.root) {
override val input: TextView get() = binding.textInputField
fun bind(
view: RelationTextValueView.Url,
actionClick: (EditGridCellAction) -> Unit
) = with(binding) {
textInputField.setText(view.value)
if (view.value.isNullOrEmpty()) {
if (view.value.isNullOrEmpty() && view.isEditable) {
textInputField.focusAndShowKeyboard()
} else {
ivActionIcon.setImageResource(R.drawable.ic_cell_relation_go_to_link)
@ -116,7 +136,7 @@ class RelationUrlHolder(
class RelationNumberHolder(
val binding: ItemObjectRelationTextBinding
) : RelationBaseHolder(binding.root) {
) : RelationTextValueViewHolderBase(binding.root) {
init {
with(binding.textInputField) {
@ -124,9 +144,11 @@ class RelationNumberHolder(
}
}
override val input: TextView get() = binding.textInputField
fun bind(view: RelationTextValueView.Number) = with(binding) {
textInputField.setText(view.value)
if (view.value.isNullOrEmpty()) {
if (view.value.isNullOrEmpty() && view.isEditable) {
textInputField.focusAndShowKeyboard()
}
btnAction.gone()

View file

@ -0,0 +1,15 @@
package com.anytypeio.anytype.core_ui.features.relations.holders
import android.text.InputType
import android.view.View
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.anytypeio.anytype.core_ui.R
abstract class RelationTextValueViewHolderBase(view: View) : RecyclerView.ViewHolder(view) {
abstract val input: TextView
fun enableReadMode() {
input.inputType = InputType.TYPE_NULL
input.setHint(R.string.empty)
}
}

View file

@ -99,7 +99,7 @@ class RelationValueAdapter(
).apply {
itemView.setOnClickListener {
val item = views[bindingAdapterPosition] as RelationValueView.Object
if (item is RelationValueView.Object.Default && !item.removeable) {
if (item is RelationValueView.Object.Default && !item.removable) {
onObjectClicked(item)
}
}
@ -117,7 +117,7 @@ class RelationValueAdapter(
).apply {
itemView.setOnClickListener {
val item = views[bindingAdapterPosition] as RelationValueView.Object
if (item is RelationValueView.Object.NonExistent && !item.removeable) {
if (item is RelationValueView.Object.NonExistent && !item.removable) {
onObjectClicked(item)
}
}
@ -135,7 +135,7 @@ class RelationValueAdapter(
).apply {
itemView.setOnClickListener {
val item = views[bindingAdapterPosition] as RelationValueView.File
if (!item.removeable) onFileClicked(item)
if (!item.removable) onFileClicked(item)
}
binding.btnRemoveFile.setOnClickListener {
val item = views[bindingAdapterPosition] as RelationValueView.File
@ -274,7 +274,7 @@ class RelationValueAdapter(
} else {
tvSubtitle.setText(R.string.unknown_object_type)
}
if (!item.removeable) {
if (!item.removable) {
btnRemoveObject.gone()
btnDragAndDropObject.gone()
} else {
@ -289,7 +289,7 @@ class RelationValueAdapter(
val binding: ItemEditCellObjectNonExistentBinding
) : ViewHolder(binding.root), DragAndDropViewHolder {
fun bind(item: RelationValueView.Object.NonExistent) = with(binding) {
if (!item.removeable) {
if (!item.removable) {
btnRemoveObject.gone()
btnDragAndDropObject.gone()
} else {
@ -305,7 +305,7 @@ class RelationValueAdapter(
tvTitle.text = "${item.name}.${item.ext}"
val mimeIcon = item.mime.getMimeIcon(item.name)
iconMime.setImageResource(mimeIcon)
if (!item.removeable) {
if (!item.removable) {
btnRemoveFile.gone()
btnDragAndDropFile.gone()
} else {

View file

@ -0,0 +1,230 @@
package com.anytypeio.anytype.core_ui.uitests
import android.content.Context
import android.os.Build
import android.text.InputType
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.core_ui.R
import com.anytypeio.anytype.core_ui.features.relations.RelationTextValueAdapter
import com.anytypeio.anytype.presentation.sets.RelationTextValueView
import com.anytypeio.anytype.test_utils.MockDataFactory
import com.anytypeio.anytype.test_utils.TestFragment
import com.anytypeio.anytype.test_utils.utils.checkHasHintText
import com.anytypeio.anytype.test_utils.utils.checkHasInputType
import com.anytypeio.anytype.test_utils.utils.checkHasText
import com.anytypeio.anytype.test_utils.utils.checkIsNotFocused
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 RelationTextValueAdapterTest {
private val context: Context = ApplicationProvider.getApplicationContext()
private lateinit var scenario: FragmentScenario<TestFragment>
@Before
fun setUp() {
context.setTheme(R.style.Theme_MaterialComponents)
scenario = launchFragmentInContainer()
}
@Test
fun `text input should not be editable - when relation with long-text format is not editable`() {
checkTextInputIsNotEditableWhenRelationIsNotEditable(
RelationTextValueView.Text(
isEditable = false,
value = MockDataFactory.randomString()
)
)
}
@Test
fun `text input should not be editable - when relation with number format is not editable`() {
checkTextInputIsNotEditableWhenRelationIsNotEditable(
RelationTextValueView.Number(
isEditable = false,
value = MockDataFactory.randomString()
)
)
}
@Test
fun `text input should not be editable - when relation with email format is not editable`() {
checkTextInputIsNotEditableWhenRelationIsNotEditable(
RelationTextValueView.Email(
isEditable = false,
value = MockDataFactory.randomString()
)
)
}
@Test
fun `text input should not be editable - when relation with url format is not editable`() {
checkTextInputIsNotEditableWhenRelationIsNotEditable(
RelationTextValueView.Url(
isEditable = false,
value = MockDataFactory.randomString()
)
)
}
@Test
fun `text input should not be editable - when relation with phone format is not editable`() {
checkTextInputIsNotEditableWhenRelationIsNotEditable(
RelationTextValueView.Phone(
isEditable = false,
value = MockDataFactory.randomString()
)
)
}
@Test
fun `text input should not be editable - when relation with short-text format is not editable`() {
checkTextInputIsNotEditableWhenRelationIsNotEditable(
RelationTextValueView.TextShort(
isEditable = false,
value = MockDataFactory.randomString()
)
)
}
@Test
fun `text input show empty-content hint - when relation with short-text format is not editable and empty`() {
textInputFieldShouldHaveSpecificHintWhenValueIsMissingAndNotEditable(
RelationTextValueView.TextShort(
isEditable = false,
value = null
)
)
}
@Test
fun `text input show empty-content hint - when relation with long-text format is not editable and empty`() {
textInputFieldShouldHaveSpecificHintWhenValueIsMissingAndNotEditable(
RelationTextValueView.Text(
isEditable = false,
value = null
)
)
}
@Test
fun `text input show empty-content hint - when relation with number format is not editable and empty`() {
textInputFieldShouldHaveSpecificHintWhenValueIsMissingAndNotEditable(
RelationTextValueView.Number(
isEditable = false,
value = null
)
)
}
@Test
fun `text input show empty-content hint - when relation with phone format is not editable and empty`() {
textInputFieldShouldHaveSpecificHintWhenValueIsMissingAndNotEditable(
RelationTextValueView.Phone(
isEditable = false,
value = null
)
)
}
@Test
fun `text input show empty-content hint - when relation with email format is not editable and empty`() {
textInputFieldShouldHaveSpecificHintWhenValueIsMissingAndNotEditable(
RelationTextValueView.Email(
isEditable = false,
value = null
)
)
}
@Test
fun `text input show empty-content hint - when relation with url format is not editable and empty`() {
textInputFieldShouldHaveSpecificHintWhenValueIsMissingAndNotEditable(
RelationTextValueView.Url(
isEditable = false,
value = null
)
)
}
//region Scenarios
private fun checkTextInputIsNotEditableWhenRelationIsNotEditable(
item: RelationTextValueView
) {
scenario.onFragment { fragment ->
// SETUP
val recycler = givenRecycler(fragment)
val adapter = givenAdapter(item)
recycler.adapter = adapter
// TESTING
TestResource.id.recycler.rVMatcher().apply {
onItemView(0, R.id.textInputField).checkHasText(item.value!!)
onItemView(0, R.id.textInputField).checkIsNotFocused()
onItemView(0, R.id.textInputField).checkHasInputType(InputType.TYPE_NULL)
}
}
}
private fun textInputFieldShouldHaveSpecificHintWhenValueIsMissingAndNotEditable(
item: RelationTextValueView
) {
scenario.onFragment { fragment ->
// SETUP
val recycler = givenRecycler(fragment)
val adapter = givenAdapter(item)
recycler.adapter = adapter
// TESTING
TestResource.id.recycler.rVMatcher().apply {
onItemView(0, R.id.textInputField).checkHasText("")
onItemView(0, R.id.textInputField).checkHasInputType(InputType.TYPE_NULL)
onItemView(0, R.id.textInputField).checkHasHintText(R.string.empty)
}
}
}
//endregion
private fun givenRecycler(fr: Fragment): RecyclerView {
val root = checkNotNull(fr.view)
return root.findViewById<RecyclerView>(TestResource.id.recycler).apply {
layoutManager = LinearLayoutManager(context)
}
}
private fun givenAdapter(item: RelationTextValueView) = RelationTextValueAdapter(
onEditCompleted = { view, txt -> },
actionClick = {},
items = listOf(item)
)
}

View file

@ -1353,7 +1353,8 @@ class EditorViewModel(
val details = orchestrator.stores.details.current().details
dispatch(
command = Command.OpenProfileMenu(
isFavorite = details[context]?.isFavorite ?: false
isFavorite = details[context]?.isFavorite ?: false,
isLocked = mode == EditorMode.Locked
)
)
}
@ -1364,6 +1365,7 @@ class EditorViewModel(
command = Command.OpenDocumentMenu(
isArchived = details[context]?.isArchived ?: false,
isFavorite = details[context]?.isFavorite ?: false,
isLocked = mode == EditorMode.Locked
)
)
}
@ -1374,6 +1376,7 @@ class EditorViewModel(
command = Command.OpenDocumentMenu(
isArchived = details[context]?.isArchived ?: false,
isFavorite = details[context]?.isFavorite ?: false,
isLocked = mode == EditorMode.Locked
)
)
}
@ -1894,7 +1897,13 @@ class EditorViewModel(
fun onDocRelationsClicked() {
Timber.d("onDocRelationsClicked, ")
dispatch(Command.OpenObjectRelationScreen.RelationList(ctx = context, target = null))
dispatch(
Command.OpenObjectRelationScreen.RelationList(
ctx = context,
target = null,
isLocked = mode == EditorMode.Locked
)
)
}
fun onSearchToolbarEvent(event: SearchInDocEvent) {
@ -3437,7 +3446,8 @@ class EditorViewModel(
Command.OpenObjectRelationScreen.Value.Text(
ctx = context,
target = context,
relation = relationId
relation = relationId,
isLocked = mode == EditorMode.Locked
)
)
}
@ -3494,7 +3504,8 @@ class EditorViewModel(
Command.OpenObjectRelationScreen.Value.Text(
ctx = context,
target = context,
relation = relationId
relation = relationId,
isLocked = mode == EditorMode.Locked
)
)
}

View file

@ -6,7 +6,7 @@ import com.anytypeio.anytype.core_models.Url
sealed class Command {
data class OpenDocumentImagePicker(val mimeType: String): Command()
data class OpenDocumentImagePicker(val mimeType: String) : Command()
data class OpenDocumentEmojiIconPicker(
val target: String
@ -54,10 +54,14 @@ sealed class Command {
data class OpenDocumentMenu(
val isArchived: Boolean,
val isFavorite: Boolean
val isFavorite: Boolean,
val isLocked: Boolean
) : Command()
data class OpenProfileMenu(val isFavorite: Boolean) : Command()
data class OpenProfileMenu(
val isFavorite: Boolean,
val isLocked: Boolean
) : Command()
data class OpenCoverGallery(val ctx: String) : Command()
data class OpenObjectLayout(val ctx: String) : Command()
@ -74,7 +78,12 @@ sealed class Command {
}
sealed class OpenObjectRelationScreen : Command() {
data class RelationList(val ctx: String, val target: String?) : OpenObjectRelationScreen()
data class RelationList(
val ctx: String,
val target: String?,
val isLocked: Boolean
) : OpenObjectRelationScreen()
data class RelationAdd(val ctx: String, val target: String) : OpenObjectRelationScreen()
sealed class Value : OpenObjectRelationScreen() {
data class Default(
@ -87,7 +96,8 @@ sealed class Command {
data class Text(
val ctx: Id,
val target: Id,
val relation: Id
val relation: Id,
val isLocked: Boolean = false
) : OpenObjectRelationScreen.Value()
data class Date(

View file

@ -98,13 +98,13 @@ fun List<ObjectWrapper.Basic>.toRelationObjectValueView(
builder = urlBuilder
),
isSelected = false,
removeable = false
removable = false
)
} else {
RelationValueView.Object.NonExistent(
id = obj.id,
isSelected = false,
removeable = false
removable = false
)
}
} else {

View file

@ -2,7 +2,6 @@ package com.anytypeio.anytype.presentation.relations
import androidx.lifecycle.viewModelScope
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.analytics.base.EventsDictionary
import com.anytypeio.anytype.analytics.base.EventsDictionary.objectRelationFeature
import com.anytypeio.anytype.analytics.base.EventsDictionary.objectRelationUnfeature
import com.anytypeio.anytype.analytics.base.EventsDictionary.relationsScreenShow
@ -23,9 +22,13 @@ import com.anytypeio.anytype.presentation.editor.Editor
import com.anytypeio.anytype.presentation.editor.editor.DetailModificationManager
import com.anytypeio.anytype.presentation.extension.sendAnalyticsRelationDeleteEvent
import com.anytypeio.anytype.presentation.extension.sendAnalyticsRelationValueEvent
import com.anytypeio.anytype.presentation.relations.model.RelationOperationError
import com.anytypeio.anytype.presentation.util.Dispatcher
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import timber.log.Timber
@ -105,6 +108,18 @@ class RelationListViewModel(
}
fun onCheckboxClicked(ctx: Id, view: DocumentRelationView) {
val isLocked = resolveIsLockedState(ctx)
if (isLocked) {
sendToast(RelationOperationError.LOCKED_OBJECT_MODIFICATION_ERROR)
} else {
proceedWithUpdatingFeaturedRelations(view, ctx)
}
}
private fun proceedWithUpdatingFeaturedRelations(
view: DocumentRelationView,
ctx: Id
) {
viewModelScope.launch {
if (view.isFeatured) {
viewModelScope.launch {
@ -163,15 +178,19 @@ class RelationListViewModel(
}
}
fun onEditOrDoneClicked() {
isEditMode.value = !isEditMode.value
views.value = views.value.map { view ->
if (view is Model.Item) {
view.copy(
isRemoveable = isEditMode.value && !Relations.defaultRelations.contains(view.view.relationId)
)
} else {
view
fun onEditOrDoneClicked(isLocked: Boolean) {
if (isLocked) {
sendToast(RelationOperationError.LOCKED_OBJECT_MODIFICATION_ERROR)
} else {
isEditMode.value = !isEditMode.value
views.value = views.value.map { view ->
if (view is Model.Item) {
view.copy(
isRemoveable = isEditMode.value && !Relations.defaultRelations.contains(view.view.relationId)
)
} else {
view
}
}
}
}
@ -210,7 +229,8 @@ class RelationListViewModel(
Command.EditTextRelationValue(
ctx = ctx,
relation = view.relationId,
target = ctx
target = ctx,
isLocked = resolveIsLockedState(ctx)
)
)
}
@ -235,7 +255,8 @@ class RelationListViewModel(
ctx = ctx,
relation = view.relationId,
target = ctx,
targetObjectTypes = relation.objectTypes
targetObjectTypes = relation.objectTypes,
isLocked = resolveIsLockedState(ctx)
)
)
}
@ -243,6 +264,11 @@ class RelationListViewModel(
}
}
private fun resolveIsLockedState(ctx: Id): Boolean {
val doc = stores.document.get().find { it.id == ctx }
return doc?.fields?.isLocked ?: false
}
private fun proceedWithTogglingRelationCheckboxValue(view: DocumentRelationView, ctx: Id) {
viewModelScope.launch {
check(view is DocumentRelationView.Checkbox)
@ -325,7 +351,8 @@ class RelationListViewModel(
data class EditTextRelationValue(
val ctx: Id,
val relation: Id,
val target: Id
val target: Id,
val isLocked: Boolean = false
) : Command()
data class EditDateRelationValue(
@ -338,7 +365,8 @@ class RelationListViewModel(
val ctx: Id,
val relation: Id,
val target: Id,
val targetObjectTypes: List<Id>
val targetObjectTypes: List<Id>,
val isLocked: Boolean = false
) : Command()
data class SetRelationKey(

View file

@ -46,7 +46,7 @@ sealed class RelationValueView {
val name: String,
val typeName: String?,
val type: String?,
val removeable: Boolean,
val removable: Boolean,
val icon: ObjectIcon,
val layout: ObjectType.Layout?,
override val isSelected: Boolean? = null,
@ -56,7 +56,7 @@ sealed class RelationValueView {
data class NonExistent(
override val id: Id,
override val isSelected: Boolean? = null,
val removeable: Boolean
val removable: Boolean
) : Object(), Selectable
}
@ -65,7 +65,7 @@ sealed class RelationValueView {
val name: String,
val mime: String,
val ext: String,
val removeable: Boolean = false,
val removable: Boolean = false,
val image: Url?,
override val isSelected: Boolean? = null,
val selectedNumber: String? = null

View file

@ -0,0 +1,5 @@
package com.anytypeio.anytype.presentation.relations.model
object RelationOperationError {
const val LOCKED_OBJECT_MODIFICATION_ERROR = "Unlock your object to edit relations"
}

View file

@ -5,6 +5,7 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.Relation
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.core_utils.ext.cancel
import com.anytypeio.anytype.presentation.number.NumberParser
import com.anytypeio.anytype.presentation.relations.providers.ObjectRelationProvider
@ -25,35 +26,57 @@ class RelationTextValueViewModel(
private val jobs = mutableListOf<Job>()
fun onStart(relationId: Id, recordId: String) {
fun onStart(
relationId: Id,
recordId: String,
isLocked: Boolean = false
) {
jobs += viewModelScope.launch {
val pipeline = combine(
relations.subscribe(relationId),
values.subscribe(recordId)
) { relation, values ->
title.value = relation.name
val value = values[relationId] as? String
val isValueReadOnly = values[Relations.IS_READ_ONLY] as? Boolean ?: false
val isValueEditable = !(isValueReadOnly || isLocked)
views.value = listOf(
when (relation.format) {
Relation.Format.SHORT_TEXT -> {
RelationTextValueView.TextShort(value = values[relationId] as? String)
RelationTextValueView.TextShort(
value = value,
isEditable = isValueEditable
)
}
Relation.Format.LONG_TEXT -> {
RelationTextValueView.Text(value = values[relationId] as? String)
RelationTextValueView.Text(
value = value,
isEditable = isValueEditable
)
}
Relation.Format.NUMBER -> {
val value = values[relationId]
RelationTextValueView.Number(
value = NumberParser.parse(value)
value = NumberParser.parse(value),
isEditable = isValueEditable
)
}
Relation.Format.URL -> {
RelationTextValueView.Url(value = values[relationId] as? String)
RelationTextValueView.Url(
value = value,
isEditable = isValueEditable
)
}
Relation.Format.EMAIL -> {
RelationTextValueView.Email(value = values[relationId] as? String)
RelationTextValueView.Email(
value = value,
isEditable = isValueEditable
)
}
Relation.Format.PHONE -> {
RelationTextValueView.Phone(value = values[relationId] as? String)
RelationTextValueView.Phone(
value = value,
isEditable = isValueEditable
)
}
else -> throw IllegalArgumentException("Wrong format:${relation.format}")
}
@ -79,12 +102,38 @@ class RelationTextValueViewModel(
}
sealed class RelationTextValueView {
data class Text(val value: String?) : RelationTextValueView()
data class TextShort(val value: String?) : RelationTextValueView()
data class Phone(val value: String?) : RelationTextValueView()
data class Url(val value: String?) : RelationTextValueView()
data class Email(val value: String?) : RelationTextValueView()
data class Number(val value: String?) : RelationTextValueView()
abstract val value: String?
abstract val isEditable: Boolean
data class Text(
override val value: String? = null,
override val isEditable: Boolean = true
) : RelationTextValueView()
data class TextShort(
override val value: String? = null,
override val isEditable: Boolean = true
) : RelationTextValueView()
data class Phone(
override val value: String? = null,
override val isEditable: Boolean = true
) : RelationTextValueView()
data class Url(
override val value: String? = null,
override val isEditable: Boolean = true
) : RelationTextValueView()
data class Email(
override val value: String? = null,
override val isEditable: Boolean = true
) : RelationTextValueView()
data class Number(
override val value: String? = null,
override val isEditable: Boolean = true
) : RelationTextValueView()
}
sealed class EditGridCellAction {

View file

@ -27,6 +27,7 @@ import com.anytypeio.anytype.presentation.navigation.AppNavigation
import com.anytypeio.anytype.presentation.objects.ObjectIcon
import com.anytypeio.anytype.presentation.objects.getProperName
import com.anytypeio.anytype.presentation.relations.RelationValueView
import com.anytypeio.anytype.presentation.relations.model.RelationOperationError
import com.anytypeio.anytype.presentation.relations.providers.ObjectDetailProvider
import com.anytypeio.anytype.presentation.relations.providers.ObjectRelationProvider
import com.anytypeio.anytype.presentation.relations.providers.ObjectValueProvider
@ -58,11 +59,11 @@ abstract class RelationValueBaseViewModel(
private var relationFormat: Relation.Format? = null
val isEditing = MutableStateFlow(false)
val isDimissed = MutableStateFlow(false)
val isDismissed = MutableStateFlow(false)
val name = MutableStateFlow("")
val views = MutableStateFlow(listOf<RelationValueView>())
val commands = MutableSharedFlow<ObjectRelationValueCommand>(replay = 0)
val isLoading = MutableStateFlow<Boolean>(false)
val isLoading = MutableStateFlow(false)
fun onStart(objectId: Id, relationId: Id) {
Timber.d("onStart")
@ -150,7 +151,7 @@ abstract class RelationValueBaseViewModel(
items.add(
RelationValueView.Object.NonExistent(
id = id,
removeable = isRemoveable
removable = isRemoveable
)
)
} else {
@ -165,7 +166,7 @@ abstract class RelationValueBaseViewModel(
layout = wrapper.layout,
builder = urlBuilder
),
removeable = isRemoveable,
removable = isRemoveable,
layout = wrapper.layout
)
)
@ -180,7 +181,7 @@ abstract class RelationValueBaseViewModel(
items.add(
RelationValueView.Object.NonExistent(
id = value,
removeable = isRemoveable
removable = isRemoveable
)
)
} else {
@ -195,7 +196,7 @@ abstract class RelationValueBaseViewModel(
layout = wrapper.layout,
builder = urlBuilder
),
removeable = isRemoveable,
removable = isRemoveable,
layout = wrapper.layout
)
)
@ -217,7 +218,7 @@ abstract class RelationValueBaseViewModel(
mime = detail?.fileMimeType.orEmpty(),
ext = detail?.fileExt.orEmpty(),
image = detail?.iconImage,
removeable = isRemoveable
removable = isRemoveable
)
)
}
@ -236,39 +237,47 @@ abstract class RelationValueBaseViewModel(
name.value = relation.name
}
fun onEditOrDoneClicked() {
isEditing.value = !isEditing.value
views.value = views.value.map { v ->
when (v) {
is RelationValueView.Object.Default -> v.copy(removeable = isEditing.value)
is RelationValueView.Object.NonExistent -> v.copy(removeable = isEditing.value)
is RelationValueView.Option.Tag -> v.copy(removable = isEditing.value)
is RelationValueView.Option.Status -> v.copy(removable = isEditing.value)
is RelationValueView.File -> v.copy(removeable = isEditing.value)
else -> v
fun onEditOrDoneClicked(isLocked: Boolean) {
if (isLocked) {
sendToast(RelationOperationError.LOCKED_OBJECT_MODIFICATION_ERROR)
} else {
isEditing.value = !isEditing.value
views.value = views.value.map { v ->
when (v) {
is RelationValueView.Object.Default -> v.copy(removable = isEditing.value)
is RelationValueView.Object.NonExistent -> v.copy(removable = isEditing.value)
is RelationValueView.Option.Tag -> v.copy(removable = isEditing.value)
is RelationValueView.Option.Status -> v.copy(removable = isEditing.value)
is RelationValueView.File -> v.copy(removable = isEditing.value)
else -> v
}
}
}
}
fun onAddValueClicked() {
when (relationFormat) {
Relation.Format.STATUS,
Relation.Format.TAG -> {
viewModelScope.launch {
commands.emit(ObjectRelationValueCommand.ShowAddStatusOrTagScreen)
fun onAddValueClicked(isLocked: Boolean) {
if (isLocked) {
sendToast(RelationOperationError.LOCKED_OBJECT_MODIFICATION_ERROR)
} else {
when (relationFormat) {
Relation.Format.STATUS,
Relation.Format.TAG -> {
viewModelScope.launch {
commands.emit(ObjectRelationValueCommand.ShowAddStatusOrTagScreen)
}
}
}
Relation.Format.FILE -> {
viewModelScope.launch {
commands.emit(ObjectRelationValueCommand.ShowFileValueActionScreen)
Relation.Format.FILE -> {
viewModelScope.launch {
commands.emit(ObjectRelationValueCommand.ShowFileValueActionScreen)
}
}
}
Relation.Format.OBJECT -> {
viewModelScope.launch {
commands.emit(ObjectRelationValueCommand.ShowAddObjectScreen)
Relation.Format.OBJECT -> {
viewModelScope.launch {
commands.emit(ObjectRelationValueCommand.ShowAddObjectScreen)
}
}
else -> {
}
}
else -> {
}
}
}
@ -277,7 +286,6 @@ abstract class RelationValueBaseViewModel(
viewModelScope.launch {
commands.emit(ObjectRelationValueCommand.ShowAddFileScreen)
}
}
fun onFileValueActionUploadFromGalleryClicked() {}

View file

@ -47,7 +47,7 @@ class EditorMenuTest : EditorPresentationTestSetup() {
vm.onDocumentMenuClicked()
observer.assertValue { value ->
value.peekContent() == Command.OpenProfileMenu(isFavorite = false)
value.peekContent() == Command.OpenProfileMenu(isFavorite = false, isLocked = false)
}
}
@ -97,7 +97,11 @@ class EditorMenuTest : EditorPresentationTestSetup() {
vm.onDocumentMenuClicked()
observer.assertValue { value ->
value.peekContent() == Command.OpenDocumentMenu(isArchived = false, isFavorite = false)
value.peekContent() == Command.OpenDocumentMenu(
isArchived = false,
isFavorite = false,
isLocked = false
)
}
}
@ -128,7 +132,8 @@ class EditorMenuTest : EditorPresentationTestSetup() {
observer.assertValue { value ->
value.peekContent() == Command.OpenDocumentMenu(
isArchived = false,
isFavorite = false
isFavorite = false,
isLocked = false
)
}
}

View file

@ -8,7 +8,9 @@ import androidx.test.espresso.action.ViewActions.typeText
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withHint
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withInputType
import com.anytypeio.anytype.test_utils.utils.TestUtils.withRecyclerView
import com.anytypeio.anytype.test_utils.utils.espresso.HasChildViewWithText
import com.anytypeio.anytype.test_utils.utils.espresso.HasViewGroupChildViewWithText
@ -31,6 +33,14 @@ fun ViewInteraction.checkHasText(text: String) {
check(matches(ViewMatchers.withText(text)))
}
fun ViewInteraction.checkHasHintText(text: Int) {
check(matches(withHint(text)))
}
fun ViewInteraction.checkHasInputType(type: Int) {
check(matches(withInputType(type)))
}
fun ViewInteraction.checkIsSelected() {
check(matches(ViewMatchers.isSelected()))
}