diff --git a/app/src/androidTest/java/com/anytypeio/anytype/features/relations/DisplayRelationObjectValueTest.kt b/app/src/androidTest/java/com/anytypeio/anytype/features/relations/DisplayRelationObjectValueTest.kt index dc604df3e2..38fa4be074 100644 --- a/app/src/androidTest/java/com/anytypeio/anytype/features/relations/DisplayRelationObjectValueTest.kt +++ b/app/src/androidTest/java/com/anytypeio/anytype/features/relations/DisplayRelationObjectValueTest.kt @@ -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, ) ) diff --git a/app/src/androidTest/java/com/anytypeio/anytype/features/relations/EditRelationTagValueTest.kt b/app/src/androidTest/java/com/anytypeio/anytype/features/relations/EditRelationTagValueTest.kt index 7c8350f2ce..a8da93de2b 100644 --- a/app/src/androidTest/java/com/anytypeio/anytype/features/relations/EditRelationTagValueTest.kt +++ b/app/src/androidTest/java/com/anytypeio/anytype/features/relations/EditRelationTagValueTest.kt @@ -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 { - return launchFragmentInContainer( + return launchFragmentInContainer( fragmentArgs = args, themeResId = R.style.AppTheme ) diff --git a/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt index db4a8e7a00..e6e297037d 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt @@ -935,6 +935,7 @@ open class EditorFragment : NavigationFragment(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(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(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(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) } diff --git a/app/src/main/java/com/anytypeio/anytype/ui/editor/sheets/ObjectMenuBaseFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/editor/sheets/ObjectMenuBaseFragment.kt index 3545cb26ea..e2bd7df295 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/editor/sheets/ObjectMenuBaseFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/editor/sheets/ObjectMenuBaseFragment.kt @@ -37,6 +37,7 @@ abstract class ObjectMenuBaseFragment : BaseBottomSheetFragment(IS_PROFILE_KEY) private val isArchived get() = arg(IS_ARCHIVED_KEY) private val isFavorite get() = arg(IS_FAVORITE_KEY) + private val isLocked get() = arg(IS_LOCKED_KEY) abstract val vm: ObjectMenuViewModelBase @@ -130,6 +131,7 @@ abstract class ObjectMenuBaseFragment : BaseBottomSheetFragment(RELATION_ID) private val objectId get() = arg(OBJECT_ID) private val flow get() = arg(FLOW_KEY) + private val isLocked get() = arg(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 diff --git a/app/src/main/java/com/anytypeio/anytype/ui/relations/RelationValueBaseFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/relations/RelationValueBaseFragment.kt index c15506f644..1830d2b3e2 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/relations/RelationValueBaseFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/relations/RelationValueBaseFragment.kt @@ -78,6 +78,7 @@ abstract class RelationValueBaseFragment : BaseBottomSheetFragment>(TARGET_TYPES_KEY) + protected val isLocked get() = arg(IS_LOCKED_KEY) abstract val vm: RelationValueBaseViewModel @@ -143,15 +144,15 @@ abstract class RelationValueBaseFragment : BaseBottomSheetFragmentWithout template Error while searching for images on Unsplash. Please try again later. Don’t forget to take and save your recovery phrase from settings + Unlock your object to add new relation + Unlock your object to edit relations diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/relations/RelationTextValueAdapter.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/relations/RelationTextValueAdapter.kt index c2d21fcaaa..302471c198 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/relations/RelationTextValueAdapter.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/relations/RelationTextValueAdapter.kt @@ -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, private val actionClick: (EditGridCellAction) -> Unit, private val onEditCompleted: (RelationTextValueView, String) -> Unit -) : RecyclerView.Adapter() { +) : RecyclerView.Adapter() { - 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 diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/relations/holders/RelationBaseHolder.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/relations/holders/RelationBaseHolder.kt deleted file mode 100644 index c727d9c239..0000000000 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/relations/holders/RelationBaseHolder.kt +++ /dev/null @@ -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) \ No newline at end of file diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/relations/holders/RelationTextHolder.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/relations/holders/RelationTextHolder.kt index d02511b932..835f3b1655 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/relations/holders/RelationTextHolder.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/relations/holders/RelationTextHolder.kt @@ -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() diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/relations/holders/RelationTextValueViewHolderBase.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/relations/holders/RelationTextValueViewHolderBase.kt new file mode 100644 index 0000000000..bd17547032 --- /dev/null +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/relations/holders/RelationTextValueViewHolderBase.kt @@ -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) + } +} \ No newline at end of file diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/sets/RelationValueAdapter.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/sets/RelationValueAdapter.kt index 62f48ddad1..3b52c1bbcb 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/sets/RelationValueAdapter.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/sets/RelationValueAdapter.kt @@ -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 { diff --git a/core-ui/src/test/java/com/anytypeio/anytype/core_ui/uitests/RelationTextValueAdapterTest.kt b/core-ui/src/test/java/com/anytypeio/anytype/core_ui/uitests/RelationTextValueAdapterTest.kt new file mode 100644 index 0000000000..ed06eeabdd --- /dev/null +++ b/core-ui/src/test/java/com/anytypeio/anytype/core_ui/uitests/RelationTextValueAdapterTest.kt @@ -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 + + @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(TestResource.id.recycler).apply { + layoutManager = LinearLayoutManager(context) + } + } + + private fun givenAdapter(item: RelationTextValueView) = RelationTextValueAdapter( + onEditCompleted = { view, txt -> }, + actionClick = {}, + items = listOf(item) + ) +} \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt index 305b413ab2..26c2d1defd 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt @@ -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 ) ) } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Command.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Command.kt index 56c86f14b2..6c23970e9e 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Command.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Command.kt @@ -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( diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/ObjectWrapperMapper.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/ObjectWrapperMapper.kt index d9d9130d2e..67c7c27784 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/ObjectWrapperMapper.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/ObjectWrapperMapper.kt @@ -98,13 +98,13 @@ fun List.toRelationObjectValueView( builder = urlBuilder ), isSelected = false, - removeable = false + removable = false ) } else { RelationValueView.Object.NonExistent( id = obj.id, isSelected = false, - removeable = false + removable = false ) } } else { diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/RelationListViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/RelationListViewModel.kt index fa6fd4b985..d56548a8d2 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/RelationListViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/RelationListViewModel.kt @@ -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 + val targetObjectTypes: List, + val isLocked: Boolean = false ) : Command() data class SetRelationKey( diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/RelationValueView.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/RelationValueView.kt index 7a3dd1d742..2e98858589 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/RelationValueView.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/RelationValueView.kt @@ -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 diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/model/RelationOperationError.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/model/RelationOperationError.kt new file mode 100644 index 0000000000..446c2383e5 --- /dev/null +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/model/RelationOperationError.kt @@ -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" +} \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/RelationTextValueViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/RelationTextValueViewModel.kt index 4cb534c158..40f7427000 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/RelationTextValueViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/RelationTextValueViewModel.kt @@ -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() - 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 { diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/RelationValueBaseViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/RelationValueBaseViewModel.kt index 8ea83af8da..e84e877433 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/RelationValueBaseViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/RelationValueBaseViewModel.kt @@ -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()) val commands = MutableSharedFlow(replay = 0) - val isLoading = MutableStateFlow(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() {} diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorMenuTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorMenuTest.kt index bc5642ccb3..796ab012da 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorMenuTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorMenuTest.kt @@ -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 ) } } diff --git a/test/android-utils/src/main/java/com/anytypeio/anytype/test_utils/utils/EspressoExt.kt b/test/android-utils/src/main/java/com/anytypeio/anytype/test_utils/utils/EspressoExt.kt index a9918b7dcc..cbcb11271a 100644 --- a/test/android-utils/src/main/java/com/anytypeio/anytype/test_utils/utils/EspressoExt.kt +++ b/test/android-utils/src/main/java/com/anytypeio/anytype/test_utils/utils/EspressoExt.kt @@ -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())) }