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:
parent
bdf231af5d
commit
c0bbae162a
25 changed files with 585 additions and 158 deletions
|
@ -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,
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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">Don’t 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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
}
|
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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() {}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue