mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-08 05:47:05 +09:00
Object | Fix | Show menu options based on type and restrictions (#2313)
This commit is contained in:
parent
16d9f6c681
commit
ecbbfb8095
13 changed files with 666 additions and 319 deletions
|
@ -12,8 +12,9 @@ import com.anytypeio.anytype.domain.objects.SetObjectIsArchived
|
|||
import com.anytypeio.anytype.presentation.common.Action
|
||||
import com.anytypeio.anytype.presentation.common.Delegator
|
||||
import com.anytypeio.anytype.presentation.editor.Editor
|
||||
import com.anytypeio.anytype.presentation.objects.ObjectMenuViewModel
|
||||
import com.anytypeio.anytype.presentation.objects.ObjectSetMenuViewModel
|
||||
import com.anytypeio.anytype.presentation.objects.menu.ObjectMenuOptionsProviderImpl
|
||||
import com.anytypeio.anytype.presentation.objects.menu.ObjectMenuViewModel
|
||||
import com.anytypeio.anytype.presentation.objects.menu.ObjectSetMenuViewModel
|
||||
import com.anytypeio.anytype.presentation.sets.ObjectSet
|
||||
import com.anytypeio.anytype.presentation.util.Dispatcher
|
||||
import com.anytypeio.anytype.ui.editor.sheets.ObjectMenuFragment
|
||||
|
@ -22,6 +23,7 @@ import dagger.Module
|
|||
import dagger.Provides
|
||||
import dagger.Subcomponent
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
|
||||
@Subcomponent(modules = [ObjectMenuModuleBase::class, ObjectMenuModule::class])
|
||||
|
@ -29,7 +31,7 @@ import kotlinx.coroutines.flow.StateFlow
|
|||
interface ObjectMenuComponent {
|
||||
@Subcomponent.Builder
|
||||
interface Builder {
|
||||
fun base(module: ObjectMenuModuleBase) : Builder
|
||||
fun base(module: ObjectMenuModuleBase): Builder
|
||||
fun module(module: ObjectMenuModule): Builder
|
||||
fun build(): ObjectMenuComponent
|
||||
}
|
||||
|
@ -42,7 +44,7 @@ interface ObjectMenuComponent {
|
|||
interface ObjectSetMenuComponent {
|
||||
@Subcomponent.Builder
|
||||
interface Builder {
|
||||
fun base(module: ObjectMenuModuleBase) : Builder
|
||||
fun base(module: ObjectMenuModuleBase): Builder
|
||||
fun module(module: ObjectSetMenuModule): Builder
|
||||
fun build(): ObjectSetMenuComponent
|
||||
}
|
||||
|
@ -57,14 +59,14 @@ object ObjectMenuModuleBase {
|
|||
@PerDialog
|
||||
fun provideAddToFavoriteUseCase(
|
||||
repo: BlockRepository
|
||||
) : AddToFavorite = AddToFavorite(repo = repo)
|
||||
): AddToFavorite = AddToFavorite(repo = repo)
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@PerDialog
|
||||
fun provideRemoveFromFavoriteUseCase(
|
||||
repo: BlockRepository
|
||||
) : RemoveFromFavorite = RemoveFromFavorite(repo = repo)
|
||||
): RemoveFromFavorite = RemoveFromFavorite(repo = repo)
|
||||
}
|
||||
|
||||
@Module
|
||||
|
@ -91,12 +93,21 @@ object ObjectMenuModule {
|
|||
analytics = analytics,
|
||||
dispatcher = dispatcher,
|
||||
updateFields = updateFields,
|
||||
delegator = delegator
|
||||
delegator = delegator,
|
||||
menuOptionsProvider = createMenuOptionsProvider(storage)
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
private fun createMenuOptionsProvider(storage: Editor.Storage) =
|
||||
ObjectMenuOptionsProviderImpl(
|
||||
details = storage.details.stream().map { it.details },
|
||||
restrictions = storage.objectRestrictions.stream()
|
||||
)
|
||||
}
|
||||
|
||||
@Module
|
||||
object ObjectSetMenuModule {
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@PerDialog
|
||||
|
@ -113,6 +124,14 @@ object ObjectSetMenuModule {
|
|||
removeFromFavorite = removeFromFavorite,
|
||||
analytics = analytics,
|
||||
state = state,
|
||||
dispatcher = dispatcher
|
||||
dispatcher = dispatcher,
|
||||
menuOptionsProvider = createMenuOptionsProvider(state)
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
private fun createMenuOptionsProvider(state: StateFlow<ObjectSet>) =
|
||||
ObjectMenuOptionsProviderImpl(
|
||||
details = state.map { it.details },
|
||||
restrictions = state.map { it.objectRestrictions }
|
||||
)
|
||||
}
|
|
@ -5,12 +5,10 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.anytypeio.anytype.R
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_ui.features.objects.ObjectActionAdapter
|
||||
import com.anytypeio.anytype.core_ui.layout.SpacingItemDecoration
|
||||
import com.anytypeio.anytype.core_ui.reactive.clicks
|
||||
|
@ -19,9 +17,8 @@ import com.anytypeio.anytype.core_utils.ext.subscribe
|
|||
import com.anytypeio.anytype.core_utils.ext.toast
|
||||
import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetFragment
|
||||
import com.anytypeio.anytype.databinding.FragmentObjectMenuBinding
|
||||
import com.anytypeio.anytype.di.common.componentManager
|
||||
import com.anytypeio.anytype.presentation.objects.ObjectMenuViewModel
|
||||
import com.anytypeio.anytype.presentation.objects.ObjectMenuViewModelBase
|
||||
import com.anytypeio.anytype.presentation.objects.menu.ObjectMenuOptionsProvider
|
||||
import com.anytypeio.anytype.presentation.objects.menu.ObjectMenuViewModelBase
|
||||
import com.anytypeio.anytype.ui.editor.cover.SelectCoverObjectFragment
|
||||
import com.anytypeio.anytype.ui.editor.cover.SelectCoverObjectSetFragment
|
||||
import com.anytypeio.anytype.ui.editor.layout.ObjectLayoutFragment
|
||||
|
@ -29,7 +26,6 @@ import com.anytypeio.anytype.ui.editor.modals.ObjectIconPickerBaseFragment
|
|||
import com.anytypeio.anytype.ui.relations.RelationListFragment
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class ObjectMenuBaseFragment : BaseBottomSheetFragment<FragmentObjectMenuBinding>() {
|
||||
|
||||
|
@ -94,16 +90,37 @@ abstract class ObjectMenuBaseFragment : BaseBottomSheetFragment<FragmentObjectMe
|
|||
jobs += subscribe(vm.toasts) { toast(it) }
|
||||
jobs += subscribe(vm.isDismissed) { isDismissed -> if (isDismissed) dismiss() }
|
||||
jobs += subscribe(vm.commands) { command -> execute(command) }
|
||||
jobs += subscribe(vm.options) { options -> renderOptions(options) }
|
||||
}
|
||||
super.onStart()
|
||||
vm.onStart(
|
||||
ctx = ctx,
|
||||
isArchived = isArchived,
|
||||
isFavorite = isFavorite,
|
||||
isProfile = isProfile
|
||||
isProfile = isProfile,
|
||||
isLocked = isLocked
|
||||
)
|
||||
}
|
||||
|
||||
// TODO refactor to recycler view
|
||||
private fun renderOptions(options: ObjectMenuOptionsProvider.Options) {
|
||||
val iconVisibility = if (options.hasIcon) View.VISIBLE else View.GONE
|
||||
val coverVisibility = if (options.hasCover) View.VISIBLE else View.GONE
|
||||
val layoutVisibility = if (options.hasLayout) View.VISIBLE else View.GONE
|
||||
val relationsVisibility = if (options.hasRelations) View.VISIBLE else View.GONE
|
||||
val historyVisibility = if (options.hasHistory) View.VISIBLE else View.GONE
|
||||
binding.optionIcon.visibility = iconVisibility
|
||||
binding.optionCover.visibility = coverVisibility
|
||||
binding.optionLayout.visibility = layoutVisibility
|
||||
binding.optionRelations.visibility = relationsVisibility
|
||||
binding.optionHistory.visibility = historyVisibility
|
||||
binding.iconDivider.visibility = iconVisibility
|
||||
binding.coverDivider.visibility = coverVisibility
|
||||
binding.layoutDivider.visibility = layoutVisibility
|
||||
binding.relationsDivider.visibility = relationsVisibility
|
||||
binding.historyDivider.visibility = historyVisibility
|
||||
}
|
||||
|
||||
private fun execute(command: ObjectMenuViewModelBase.Command) {
|
||||
when (command) {
|
||||
ObjectMenuViewModelBase.Command.OpenObjectCover -> {
|
||||
|
@ -186,28 +203,4 @@ abstract class ObjectMenuBaseFragment : BaseBottomSheetFragment<FragmentObjectMe
|
|||
fun onLayoutClicked()
|
||||
fun onUndoRedoClicked()
|
||||
}
|
||||
}
|
||||
|
||||
class ObjectMenuFragment : ObjectMenuBaseFragment() {
|
||||
|
||||
@Inject
|
||||
lateinit var factory: ObjectMenuViewModel.Factory
|
||||
override val vm by viewModels<ObjectMenuViewModel> { factory }
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
with(lifecycleScope) {
|
||||
subscribe(vm.isObjectArchived) { isArchived ->
|
||||
if (isArchived) parentFragment?.findNavController()?.popBackStack()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun injectDependencies() {
|
||||
componentManager().objectMenuComponent.get(ctx).inject(this)
|
||||
}
|
||||
|
||||
override fun releaseDependencies() {
|
||||
componentManager().objectMenuComponent.release(ctx)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package com.anytypeio.anytype.ui.editor.sheets
|
||||
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.anytypeio.anytype.core_utils.ext.subscribe
|
||||
import com.anytypeio.anytype.di.common.componentManager
|
||||
import com.anytypeio.anytype.presentation.objects.menu.ObjectMenuViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
class ObjectMenuFragment : ObjectMenuBaseFragment() {
|
||||
|
||||
@Inject
|
||||
lateinit var factory: ObjectMenuViewModel.Factory
|
||||
override val vm by viewModels<ObjectMenuViewModel> { factory }
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
with(lifecycleScope) {
|
||||
subscribe(vm.isObjectArchived) { isArchived ->
|
||||
if (isArchived) parentFragment?.findNavController()?.popBackStack()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun injectDependencies() {
|
||||
componentManager().objectMenuComponent.get(ctx).inject(this)
|
||||
}
|
||||
|
||||
override fun releaseDependencies() {
|
||||
componentManager().objectMenuComponent.release(ctx)
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@ import androidx.navigation.fragment.findNavController
|
|||
import com.anytypeio.anytype.R
|
||||
import com.anytypeio.anytype.core_utils.ext.subscribe
|
||||
import com.anytypeio.anytype.di.common.componentManager
|
||||
import com.anytypeio.anytype.presentation.objects.ObjectSetMenuViewModel
|
||||
import com.anytypeio.anytype.presentation.objects.menu.ObjectSetMenuViewModel
|
||||
import com.anytypeio.anytype.ui.editor.sheets.ObjectMenuBaseFragment
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
app:title="@string/icon" />
|
||||
|
||||
<View
|
||||
android:id="@+id/divider1"
|
||||
android:id="@+id/iconDivider"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0.5dp"
|
||||
android:layout_marginStart="72dp"
|
||||
|
@ -48,12 +48,12 @@
|
|||
app:icon="@drawable/ic_object_menu_cover"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/divider1"
|
||||
app:layout_constraintTop_toBottomOf="@id/iconDivider"
|
||||
app:subtitle="@string/cover_description"
|
||||
app:title="@string/cover" />
|
||||
|
||||
<View
|
||||
android:id="@+id/divider2"
|
||||
android:id="@+id/coverDivider"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0.5dp"
|
||||
android:layout_marginStart="72dp"
|
||||
|
@ -73,12 +73,12 @@
|
|||
app:icon="@drawable/ic_object_menu_layout"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/divider2"
|
||||
app:layout_constraintTop_toBottomOf="@id/coverDivider"
|
||||
app:subtitle="@string/layout_description"
|
||||
app:title="@string/layout" />
|
||||
|
||||
<View
|
||||
android:id="@+id/divider3"
|
||||
android:id="@+id/layoutDivider"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0.5dp"
|
||||
android:layout_marginStart="72dp"
|
||||
|
@ -98,12 +98,12 @@
|
|||
app:icon="@drawable/ic_object_menu_relations"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/divider3"
|
||||
app:layout_constraintTop_toBottomOf="@id/layoutDivider"
|
||||
app:subtitle="@string/relations_description"
|
||||
app:title="@string/relations" />
|
||||
|
||||
<View
|
||||
android:id="@+id/divider4"
|
||||
android:id="@+id/relationsDivider"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0.5dp"
|
||||
android:layout_marginStart="72dp"
|
||||
|
@ -123,12 +123,12 @@
|
|||
app:icon="@drawable/ic_object_menu_history"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/divider4"
|
||||
app:layout_constraintTop_toBottomOf="@id/relationsDivider"
|
||||
app:subtitle="@string/history_description"
|
||||
app:title="@string/history" />
|
||||
|
||||
<View
|
||||
android:id="@+id/divider5"
|
||||
android:id="@+id/historyDivider"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0.5dp"
|
||||
android:layout_marginTop="21dp"
|
||||
|
@ -144,7 +144,7 @@
|
|||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/divider5">
|
||||
app:layout_constraintTop_toBottomOf="@+id/historyDivider">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:layout_gravity="center_vertical"
|
||||
|
|
|
@ -33,7 +33,7 @@ data class ObjectType(
|
|||
IMAGE(8),
|
||||
NOTE(9),
|
||||
SPACE(10),
|
||||
DATABASE(20)
|
||||
DATABASE(20),
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -48,7 +48,7 @@ sealed class ObjectWrapper {
|
|||
|
||||
val layout: ObjectType.Layout?
|
||||
get() = when (val value = map[Relations.LAYOUT]) {
|
||||
is Double -> ObjectType.Layout.values().find { layout ->
|
||||
is Double -> ObjectType.Layout.values().singleOrNull { layout ->
|
||||
layout.code == value.toInt()
|
||||
}
|
||||
else -> null
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
package com.anytypeio.anytype.presentation.objects.menu
|
||||
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface ObjectMenuOptionsProvider {
|
||||
|
||||
data class Options(
|
||||
val hasIcon: Boolean,
|
||||
val hasCover: Boolean,
|
||||
val hasLayout: Boolean,
|
||||
val hasRelations: Boolean,
|
||||
val hasHistory: Boolean
|
||||
) {
|
||||
companion object {
|
||||
val ALL = Options(
|
||||
hasIcon = true,
|
||||
hasCover = true,
|
||||
hasLayout = true,
|
||||
hasRelations = true,
|
||||
hasHistory = true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun provide(ctx: Id, isLocked: Boolean): Flow<Options>
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
package com.anytypeio.anytype.presentation.objects.menu
|
||||
|
||||
import com.anytypeio.anytype.core_models.Block
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.ObjectType
|
||||
import com.anytypeio.anytype.core_models.ObjectWrapper
|
||||
import com.anytypeio.anytype.core_models.restrictions.ObjectRestriction
|
||||
import com.anytypeio.anytype.presentation.objects.menu.ObjectMenuOptionsProvider.Options
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class ObjectMenuOptionsProviderImpl(
|
||||
private val details: Flow<Map<Id, Block.Fields>>,
|
||||
private val restrictions: Flow<List<ObjectRestriction>>,
|
||||
) : ObjectMenuOptionsProvider {
|
||||
|
||||
private fun observeLayout(ctx: Id): Flow<ObjectType.Layout?> {
|
||||
return details
|
||||
.map { details ->
|
||||
val fields = requireNotNull(details[ctx]) {
|
||||
"Can't find details by objectId=$ctx"
|
||||
}
|
||||
ObjectWrapper.Basic(fields.map).layout
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun provide(ctx: Id, isLocked: Boolean): Flow<Options> {
|
||||
return combine(observeLayout(ctx), restrictions) { layout, restrictions ->
|
||||
createOptions(layout, restrictions, isLocked)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createOptions(
|
||||
layout: ObjectType.Layout?,
|
||||
restrictions: List<ObjectRestriction>,
|
||||
isLocked: Boolean,
|
||||
): Options {
|
||||
val hasIcon = !isLocked
|
||||
val hasCover = !isLocked
|
||||
val hasLayout = !isLocked && !restrictions.contains(ObjectRestriction.LAYOUT_CHANGE)
|
||||
val options = if (layout != null) {
|
||||
when (layout) {
|
||||
ObjectType.Layout.BASIC,
|
||||
ObjectType.Layout.PROFILE,
|
||||
ObjectType.Layout.OBJECT_TYPE,
|
||||
ObjectType.Layout.RELATION,
|
||||
ObjectType.Layout.FILE,
|
||||
ObjectType.Layout.DASHBOARD,
|
||||
ObjectType.Layout.IMAGE,
|
||||
ObjectType.Layout.SPACE,
|
||||
ObjectType.Layout.SET,
|
||||
ObjectType.Layout.DATABASE -> Options.ALL.copy(
|
||||
hasIcon = hasIcon,
|
||||
hasCover = hasCover,
|
||||
hasLayout = hasLayout,
|
||||
)
|
||||
ObjectType.Layout.TODO -> Options(
|
||||
hasIcon = false,
|
||||
hasCover = hasCover,
|
||||
hasLayout = hasLayout,
|
||||
hasRelations = true,
|
||||
hasHistory = true
|
||||
)
|
||||
|
||||
ObjectType.Layout.NOTE -> Options(
|
||||
hasIcon = false,
|
||||
hasCover = false,
|
||||
hasLayout = hasLayout,
|
||||
hasRelations = true,
|
||||
hasHistory = true
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// unknown layout show all options
|
||||
Options.ALL.copy(
|
||||
hasIcon = hasIcon,
|
||||
hasCover = hasCover,
|
||||
hasLayout = hasLayout,
|
||||
)
|
||||
}
|
||||
return options
|
||||
}
|
||||
}
|
|
@ -1,11 +1,10 @@
|
|||
package com.anytypeio.anytype.presentation.objects
|
||||
package com.anytypeio.anytype.presentation.objects.menu
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.anytypeio.anytype.analytics.base.Analytics
|
||||
import com.anytypeio.anytype.analytics.base.EventsDictionary.objectLock
|
||||
import com.anytypeio.anytype.analytics.base.EventsDictionary.objectUnlock
|
||||
import com.anytypeio.anytype.analytics.base.EventsDictionary
|
||||
import com.anytypeio.anytype.analytics.base.sendEvent
|
||||
import com.anytypeio.anytype.core_models.Block
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
|
@ -17,154 +16,19 @@ import com.anytypeio.anytype.domain.dashboard.interactor.AddToFavorite
|
|||
import com.anytypeio.anytype.domain.dashboard.interactor.RemoveFromFavorite
|
||||
import com.anytypeio.anytype.domain.objects.SetObjectIsArchived
|
||||
import com.anytypeio.anytype.presentation.common.Action
|
||||
import com.anytypeio.anytype.presentation.common.BaseViewModel
|
||||
import com.anytypeio.anytype.presentation.common.Delegator
|
||||
import com.anytypeio.anytype.presentation.editor.Editor
|
||||
import com.anytypeio.anytype.presentation.extension.sendAnalyticsAddToFavoritesEvent
|
||||
import com.anytypeio.anytype.presentation.extension.sendAnalyticsMoveToBinEvent
|
||||
import com.anytypeio.anytype.presentation.extension.sendAnalyticsRemoveFromFavoritesEvent
|
||||
import com.anytypeio.anytype.presentation.sets.ObjectSet
|
||||
import com.anytypeio.anytype.presentation.objects.ObjectAction
|
||||
import com.anytypeio.anytype.presentation.util.Dispatcher
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
abstract class ObjectMenuViewModelBase(
|
||||
private val setObjectIsArchived: SetObjectIsArchived,
|
||||
private val addToFavorite: AddToFavorite,
|
||||
private val removeFromFavorite: RemoveFromFavorite,
|
||||
protected val dispatcher: Dispatcher<Payload>,
|
||||
private val analytics: Analytics
|
||||
) : BaseViewModel() {
|
||||
|
||||
val isDismissed = MutableStateFlow(false)
|
||||
val isObjectArchived = MutableStateFlow(false)
|
||||
val commands = MutableSharedFlow<Command>(replay = 0)
|
||||
val actions = MutableStateFlow(emptyList<ObjectAction>())
|
||||
|
||||
abstract fun onIconClicked(ctx: Id)
|
||||
abstract fun onCoverClicked(ctx: Id)
|
||||
abstract fun onLayoutClicked(ctx: Id)
|
||||
abstract fun onRelationsClicked()
|
||||
abstract fun onHistoryClicked()
|
||||
fun onStart(
|
||||
ctx: Id,
|
||||
isFavorite: Boolean,
|
||||
isArchived: Boolean,
|
||||
isProfile: Boolean
|
||||
) {
|
||||
actions.value = buildActions(
|
||||
ctx = ctx,
|
||||
isArchived = isArchived,
|
||||
isFavorite = isFavorite,
|
||||
isProfile = isProfile
|
||||
)
|
||||
}
|
||||
abstract fun onActionClicked(ctx: Id, action: ObjectAction)
|
||||
|
||||
abstract fun buildActions(
|
||||
ctx: Id,
|
||||
isArchived: Boolean,
|
||||
isFavorite: Boolean,
|
||||
isProfile: Boolean
|
||||
): MutableList<ObjectAction>
|
||||
|
||||
protected fun proceedWithRemovingFromFavorites(ctx: Id) {
|
||||
viewModelScope.launch {
|
||||
removeFromFavorite(
|
||||
RemoveFromFavorite.Params(
|
||||
target = ctx
|
||||
)
|
||||
).process(
|
||||
failure = { Timber.e(it, "Error while removing from favorite.") },
|
||||
success = {
|
||||
sendAnalyticsRemoveFromFavoritesEvent(analytics)
|
||||
dispatcher.send(it)
|
||||
_toasts.emit(REMOVE_FROM_FAVORITE_SUCCESS_MSG).also {
|
||||
isDismissed.value = true
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun proceedWithAddingToFavorites(ctx: Id) {
|
||||
viewModelScope.launch {
|
||||
addToFavorite(
|
||||
AddToFavorite.Params(
|
||||
target = ctx
|
||||
)
|
||||
).process(
|
||||
failure = { Timber.e(it, "Error while adding to favorites.") },
|
||||
success = {
|
||||
sendAnalyticsAddToFavoritesEvent(analytics)
|
||||
dispatcher.send(it)
|
||||
_toasts.emit(ADD_TO_FAVORITE_SUCCESS_MSG).also {
|
||||
isDismissed.value = true
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun proceedWithUpdatingArchivedStatus(ctx: Id, isArchived: Boolean) {
|
||||
viewModelScope.launch {
|
||||
setObjectIsArchived(
|
||||
SetObjectIsArchived.Params(
|
||||
context = ctx,
|
||||
isArchived = isArchived
|
||||
)
|
||||
).process(
|
||||
failure = {
|
||||
Timber.e(it, ARCHIVE_OBJECT_ERR_MSG)
|
||||
_toasts.emit(ARCHIVE_OBJECT_ERR_MSG)
|
||||
},
|
||||
success = {
|
||||
if (isArchived) {
|
||||
sendAnalyticsMoveToBinEvent(analytics)
|
||||
_toasts.emit(ARCHIVE_OBJECT_SUCCESS_MSG)
|
||||
} else {
|
||||
_toasts.emit(RESTORE_OBJECT_SUCCESS_MSG)
|
||||
}
|
||||
isObjectArchived.value = true
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Command {
|
||||
object OpenObjectIcons : Command()
|
||||
object OpenSetIcons : Command()
|
||||
object OpenObjectCover : Command()
|
||||
object OpenSetCover : Command()
|
||||
object OpenObjectLayout : Command()
|
||||
object OpenSetLayout : Command()
|
||||
object OpenObjectRelations : Command()
|
||||
object OpenSetRelations : Command()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ARCHIVE_OBJECT_SUCCESS_MSG = "Object archived!"
|
||||
const val RESTORE_OBJECT_SUCCESS_MSG = "Object restored!"
|
||||
const val ARCHIVE_OBJECT_ERR_MSG =
|
||||
"Error while changing is-archived status for this object. Please, try again later."
|
||||
const val ADD_TO_FAVORITE_SUCCESS_MSG = "Object added to favorites."
|
||||
const val REMOVE_FROM_FAVORITE_SUCCESS_MSG = "Object removed from favorites."
|
||||
const val COMING_SOON_MSG = "Coming soon..."
|
||||
const val NOT_ALLOWED = "Not allowed for this object"
|
||||
const val OBJECT_IS_LOCKED_MSG = "Your object is locked"
|
||||
const val OBJECT_IS_UNLOCKED_MSG = "Your object is locked"
|
||||
const val SOMETHING_WENT_WRONG_MSG = "Something went wrong. Please, try again later."
|
||||
}
|
||||
}
|
||||
|
||||
class ObjectMenuViewModel(
|
||||
setObjectIsArchived: SetObjectIsArchived,
|
||||
addToFavorite: AddToFavorite,
|
||||
removeFromFavorite: RemoveFromFavorite,
|
||||
dispatcher: Dispatcher<Payload>,
|
||||
menuOptionsProvider: ObjectMenuOptionsProvider,
|
||||
private val duplicateObject: DuplicateObject,
|
||||
private val storage: Editor.Storage,
|
||||
private val analytics: Analytics,
|
||||
|
@ -175,7 +39,8 @@ class ObjectMenuViewModel(
|
|||
addToFavorite = addToFavorite,
|
||||
removeFromFavorite = removeFromFavorite,
|
||||
dispatcher = dispatcher,
|
||||
analytics = analytics
|
||||
analytics = analytics,
|
||||
menuOptionsProvider = menuOptionsProvider
|
||||
) {
|
||||
|
||||
private val objectRestrictions = storage.objectRestrictions.current()
|
||||
|
@ -185,7 +50,7 @@ class ObjectMenuViewModel(
|
|||
isArchived: Boolean,
|
||||
isFavorite: Boolean,
|
||||
isProfile: Boolean
|
||||
): MutableList<ObjectAction> = mutableListOf<ObjectAction>().apply {
|
||||
): List<ObjectAction> = buildList {
|
||||
if (!isProfile) {
|
||||
if (isArchived) {
|
||||
add(ObjectAction.RESTORE)
|
||||
|
@ -365,7 +230,7 @@ class ObjectMenuViewModel(
|
|||
if (isLocked) {
|
||||
sendEvent(
|
||||
analytics = analytics,
|
||||
eventName = objectLock
|
||||
eventName = EventsDictionary.objectLock
|
||||
)
|
||||
_toasts.emit(OBJECT_IS_LOCKED_MSG).also {
|
||||
isDismissed.value = true
|
||||
|
@ -373,7 +238,7 @@ class ObjectMenuViewModel(
|
|||
} else {
|
||||
sendEvent(
|
||||
analytics = analytics,
|
||||
eventName = objectUnlock
|
||||
eventName = EventsDictionary.objectUnlock
|
||||
)
|
||||
_toasts.emit(OBJECT_IS_UNLOCKED_MSG).also {
|
||||
isDismissed.value = true
|
||||
|
@ -405,7 +270,8 @@ class ObjectMenuViewModel(
|
|||
private val analytics: Analytics,
|
||||
private val dispatcher: Dispatcher<Payload>,
|
||||
private val updateFields: UpdateFields,
|
||||
private val delegator: Delegator<Action>
|
||||
private val delegator: Delegator<Action>,
|
||||
private val menuOptionsProvider: ObjectMenuOptionsProvider
|
||||
) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return ObjectMenuViewModel(
|
||||
|
@ -417,129 +283,9 @@ class ObjectMenuViewModel(
|
|||
analytics = analytics,
|
||||
dispatcher = dispatcher,
|
||||
updateFields = updateFields,
|
||||
delegator = delegator
|
||||
delegator = delegator,
|
||||
menuOptionsProvider = menuOptionsProvider
|
||||
) as T
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ObjectSetMenuViewModel(
|
||||
setObjectIsArchived: SetObjectIsArchived,
|
||||
addToFavorite: AddToFavorite,
|
||||
removeFromFavorite: RemoveFromFavorite,
|
||||
dispatcher: Dispatcher<Payload>,
|
||||
private val analytics: Analytics,
|
||||
state: StateFlow<ObjectSet>
|
||||
) : ObjectMenuViewModelBase(
|
||||
setObjectIsArchived = setObjectIsArchived,
|
||||
addToFavorite = addToFavorite,
|
||||
removeFromFavorite = removeFromFavorite,
|
||||
dispatcher = dispatcher,
|
||||
analytics = analytics
|
||||
) {
|
||||
|
||||
private val objectRestrictions = state.value.objectRestrictions
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
class Factory(
|
||||
private val setObjectIsArchived: SetObjectIsArchived,
|
||||
private val addToFavorite: AddToFavorite,
|
||||
private val removeFromFavorite: RemoveFromFavorite,
|
||||
private val dispatcher: Dispatcher<Payload>,
|
||||
private val analytics: Analytics,
|
||||
private val state: StateFlow<ObjectSet>
|
||||
) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return ObjectSetMenuViewModel(
|
||||
setObjectIsArchived = setObjectIsArchived,
|
||||
addToFavorite = addToFavorite,
|
||||
removeFromFavorite = removeFromFavorite,
|
||||
analytics = analytics,
|
||||
state = state,
|
||||
dispatcher = dispatcher
|
||||
) as T
|
||||
}
|
||||
}
|
||||
|
||||
override fun onIconClicked(ctx: Id) {
|
||||
viewModelScope.launch {
|
||||
if (objectRestrictions.contains(ObjectRestriction.DETAILS)) {
|
||||
_toasts.emit(NOT_ALLOWED)
|
||||
} else {
|
||||
commands.emit(Command.OpenSetIcons)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCoverClicked(ctx: Id) {
|
||||
viewModelScope.launch {
|
||||
if (objectRestrictions.contains(ObjectRestriction.DETAILS)) {
|
||||
_toasts.emit(NOT_ALLOWED)
|
||||
} else {
|
||||
commands.emit(Command.OpenSetCover)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLayoutClicked(ctx: Id) {
|
||||
viewModelScope.launch {
|
||||
if (objectRestrictions.contains(ObjectRestriction.LAYOUT_CHANGE)) {
|
||||
_toasts.emit(NOT_ALLOWED)
|
||||
} else {
|
||||
commands.emit(Command.OpenSetLayout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRelationsClicked() {
|
||||
viewModelScope.launch {
|
||||
if (objectRestrictions.contains(ObjectRestriction.RELATIONS)) {
|
||||
_toasts.emit(NOT_ALLOWED)
|
||||
} else {
|
||||
commands.emit(Command.OpenSetRelations)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onHistoryClicked() {
|
||||
viewModelScope.launch { _toasts.emit(COMING_SOON_MSG) }
|
||||
}
|
||||
|
||||
override fun buildActions(
|
||||
ctx: Id,
|
||||
isArchived: Boolean,
|
||||
isFavorite: Boolean,
|
||||
isProfile: Boolean
|
||||
): MutableList<ObjectAction> = mutableListOf<ObjectAction>().apply {
|
||||
if (isArchived) {
|
||||
add(ObjectAction.RESTORE)
|
||||
} else {
|
||||
add(ObjectAction.DELETE)
|
||||
}
|
||||
if (isFavorite) {
|
||||
add(ObjectAction.REMOVE_FROM_FAVOURITE)
|
||||
} else {
|
||||
add(ObjectAction.ADD_TO_FAVOURITE)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActionClicked(ctx: Id, action: ObjectAction) {
|
||||
when (action) {
|
||||
ObjectAction.DELETE -> {
|
||||
proceedWithUpdatingArchivedStatus(ctx = ctx, isArchived = true)
|
||||
}
|
||||
ObjectAction.RESTORE -> {
|
||||
proceedWithUpdatingArchivedStatus(ctx = ctx, isArchived = false)
|
||||
}
|
||||
ObjectAction.ADD_TO_FAVOURITE -> {
|
||||
proceedWithAddingToFavorites(ctx)
|
||||
}
|
||||
ObjectAction.REMOVE_FROM_FAVOURITE -> {
|
||||
proceedWithRemovingFromFavorites(ctx)
|
||||
}
|
||||
else -> {
|
||||
viewModelScope.launch { _toasts.emit(COMING_SOON_MSG) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,175 @@
|
|||
package com.anytypeio.anytype.presentation.objects.menu
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.anytypeio.anytype.analytics.base.Analytics
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.Payload
|
||||
import com.anytypeio.anytype.domain.dashboard.interactor.AddToFavorite
|
||||
import com.anytypeio.anytype.domain.dashboard.interactor.RemoveFromFavorite
|
||||
import com.anytypeio.anytype.domain.objects.SetObjectIsArchived
|
||||
import com.anytypeio.anytype.presentation.common.BaseViewModel
|
||||
import com.anytypeio.anytype.presentation.extension.sendAnalyticsAddToFavoritesEvent
|
||||
import com.anytypeio.anytype.presentation.extension.sendAnalyticsMoveToBinEvent
|
||||
import com.anytypeio.anytype.presentation.extension.sendAnalyticsRemoveFromFavoritesEvent
|
||||
import com.anytypeio.anytype.presentation.objects.ObjectAction
|
||||
import com.anytypeio.anytype.presentation.util.Dispatcher
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
abstract class ObjectMenuViewModelBase(
|
||||
private val setObjectIsArchived: SetObjectIsArchived,
|
||||
private val addToFavorite: AddToFavorite,
|
||||
private val removeFromFavorite: RemoveFromFavorite,
|
||||
protected val dispatcher: Dispatcher<Payload>,
|
||||
private val analytics: Analytics,
|
||||
private val menuOptionsProvider: ObjectMenuOptionsProvider,
|
||||
) : BaseViewModel() {
|
||||
|
||||
private val jobs = mutableListOf<Job>()
|
||||
val isDismissed = MutableStateFlow(false)
|
||||
val isObjectArchived = MutableStateFlow(false)
|
||||
val commands = MutableSharedFlow<Command>(replay = 0)
|
||||
val actions = MutableStateFlow(emptyList<ObjectAction>())
|
||||
|
||||
private val _options = MutableStateFlow(
|
||||
ObjectMenuOptionsProvider.Options(
|
||||
hasIcon = false,
|
||||
hasCover = false,
|
||||
hasLayout = false,
|
||||
hasRelations = false,
|
||||
hasHistory = false
|
||||
)
|
||||
)
|
||||
val options: Flow<ObjectMenuOptionsProvider.Options> = _options
|
||||
|
||||
abstract fun onIconClicked(ctx: Id)
|
||||
abstract fun onCoverClicked(ctx: Id)
|
||||
abstract fun onLayoutClicked(ctx: Id)
|
||||
abstract fun onRelationsClicked()
|
||||
abstract fun onHistoryClicked()
|
||||
|
||||
fun onStop() {
|
||||
jobs.forEach(Job::cancel)
|
||||
jobs.clear()
|
||||
}
|
||||
|
||||
fun onStart(
|
||||
ctx: Id,
|
||||
isFavorite: Boolean,
|
||||
isArchived: Boolean,
|
||||
isProfile: Boolean,
|
||||
isLocked: Boolean
|
||||
) {
|
||||
actions.value = buildActions(
|
||||
ctx = ctx,
|
||||
isArchived = isArchived,
|
||||
isFavorite = isFavorite,
|
||||
isProfile = isProfile
|
||||
)
|
||||
jobs += viewModelScope.launch {
|
||||
menuOptionsProvider.provide(ctx, isLocked).collect(_options)
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun onActionClicked(ctx: Id, action: ObjectAction)
|
||||
|
||||
abstract fun buildActions(
|
||||
ctx: Id,
|
||||
isArchived: Boolean,
|
||||
isFavorite: Boolean,
|
||||
isProfile: Boolean
|
||||
): List<ObjectAction>
|
||||
|
||||
protected fun proceedWithRemovingFromFavorites(ctx: Id) {
|
||||
viewModelScope.launch {
|
||||
removeFromFavorite(
|
||||
RemoveFromFavorite.Params(
|
||||
target = ctx
|
||||
)
|
||||
).process(
|
||||
failure = { Timber.e(it, "Error while removing from favorite.") },
|
||||
success = {
|
||||
sendAnalyticsRemoveFromFavoritesEvent(analytics)
|
||||
dispatcher.send(it)
|
||||
_toasts.emit(REMOVE_FROM_FAVORITE_SUCCESS_MSG).also {
|
||||
isDismissed.value = true
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun proceedWithAddingToFavorites(ctx: Id) {
|
||||
viewModelScope.launch {
|
||||
addToFavorite(
|
||||
AddToFavorite.Params(
|
||||
target = ctx
|
||||
)
|
||||
).process(
|
||||
failure = { Timber.e(it, "Error while adding to favorites.") },
|
||||
success = {
|
||||
sendAnalyticsAddToFavoritesEvent(analytics)
|
||||
dispatcher.send(it)
|
||||
_toasts.emit(ADD_TO_FAVORITE_SUCCESS_MSG).also {
|
||||
isDismissed.value = true
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun proceedWithUpdatingArchivedStatus(ctx: Id, isArchived: Boolean) {
|
||||
viewModelScope.launch {
|
||||
setObjectIsArchived(
|
||||
SetObjectIsArchived.Params(
|
||||
context = ctx,
|
||||
isArchived = isArchived
|
||||
)
|
||||
).process(
|
||||
failure = {
|
||||
Timber.e(it, ARCHIVE_OBJECT_ERR_MSG)
|
||||
_toasts.emit(ARCHIVE_OBJECT_ERR_MSG)
|
||||
},
|
||||
success = {
|
||||
if (isArchived) {
|
||||
sendAnalyticsMoveToBinEvent(analytics)
|
||||
_toasts.emit(ARCHIVE_OBJECT_SUCCESS_MSG)
|
||||
} else {
|
||||
_toasts.emit(RESTORE_OBJECT_SUCCESS_MSG)
|
||||
}
|
||||
isObjectArchived.value = true
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Command {
|
||||
object OpenObjectIcons : Command()
|
||||
object OpenSetIcons : Command()
|
||||
object OpenObjectCover : Command()
|
||||
object OpenSetCover : Command()
|
||||
object OpenObjectLayout : Command()
|
||||
object OpenSetLayout : Command()
|
||||
object OpenObjectRelations : Command()
|
||||
object OpenSetRelations : Command()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ARCHIVE_OBJECT_SUCCESS_MSG = "Object archived!"
|
||||
const val RESTORE_OBJECT_SUCCESS_MSG = "Object restored!"
|
||||
const val ARCHIVE_OBJECT_ERR_MSG =
|
||||
"Error while changing is-archived status for this object. Please, try again later."
|
||||
const val ADD_TO_FAVORITE_SUCCESS_MSG = "Object added to favorites."
|
||||
const val REMOVE_FROM_FAVORITE_SUCCESS_MSG = "Object removed from favorites."
|
||||
const val COMING_SOON_MSG = "Coming soon..."
|
||||
const val NOT_ALLOWED = "Not allowed for this object"
|
||||
const val OBJECT_IS_LOCKED_MSG = "Your object is locked"
|
||||
const val OBJECT_IS_UNLOCKED_MSG = "Your object is locked"
|
||||
const val SOMETHING_WENT_WRONG_MSG = "Something went wrong. Please, try again later."
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
package com.anytypeio.anytype.presentation.objects.menu
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.anytypeio.anytype.analytics.base.Analytics
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.Payload
|
||||
import com.anytypeio.anytype.core_models.restrictions.ObjectRestriction
|
||||
import com.anytypeio.anytype.domain.dashboard.interactor.AddToFavorite
|
||||
import com.anytypeio.anytype.domain.dashboard.interactor.RemoveFromFavorite
|
||||
import com.anytypeio.anytype.domain.objects.SetObjectIsArchived
|
||||
import com.anytypeio.anytype.presentation.objects.ObjectAction
|
||||
import com.anytypeio.anytype.presentation.sets.ObjectSet
|
||||
import com.anytypeio.anytype.presentation.util.Dispatcher
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ObjectSetMenuViewModel(
|
||||
setObjectIsArchived: SetObjectIsArchived,
|
||||
addToFavorite: AddToFavorite,
|
||||
removeFromFavorite: RemoveFromFavorite,
|
||||
dispatcher: Dispatcher<Payload>,
|
||||
state: StateFlow<ObjectSet>,
|
||||
menuOptionsProvider: ObjectMenuOptionsProvider,
|
||||
private val analytics: Analytics,
|
||||
) : ObjectMenuViewModelBase(
|
||||
setObjectIsArchived = setObjectIsArchived,
|
||||
addToFavorite = addToFavorite,
|
||||
removeFromFavorite = removeFromFavorite,
|
||||
dispatcher = dispatcher,
|
||||
analytics = analytics,
|
||||
menuOptionsProvider = menuOptionsProvider,
|
||||
) {
|
||||
|
||||
private val objectRestrictions = state.value.objectRestrictions
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
class Factory(
|
||||
private val setObjectIsArchived: SetObjectIsArchived,
|
||||
private val addToFavorite: AddToFavorite,
|
||||
private val removeFromFavorite: RemoveFromFavorite,
|
||||
private val dispatcher: Dispatcher<Payload>,
|
||||
private val analytics: Analytics,
|
||||
private val state: StateFlow<ObjectSet>,
|
||||
private val menuOptionsProvider: ObjectMenuOptionsProvider,
|
||||
) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return ObjectSetMenuViewModel(
|
||||
setObjectIsArchived = setObjectIsArchived,
|
||||
addToFavorite = addToFavorite,
|
||||
removeFromFavorite = removeFromFavorite,
|
||||
analytics = analytics,
|
||||
state = state,
|
||||
dispatcher = dispatcher,
|
||||
menuOptionsProvider = menuOptionsProvider
|
||||
) as T
|
||||
}
|
||||
}
|
||||
|
||||
override fun onIconClicked(ctx: Id) {
|
||||
viewModelScope.launch {
|
||||
if (objectRestrictions.contains(ObjectRestriction.DETAILS)) {
|
||||
_toasts.emit(NOT_ALLOWED)
|
||||
} else {
|
||||
commands.emit(Command.OpenSetIcons)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCoverClicked(ctx: Id) {
|
||||
viewModelScope.launch {
|
||||
if (objectRestrictions.contains(ObjectRestriction.DETAILS)) {
|
||||
_toasts.emit(NOT_ALLOWED)
|
||||
} else {
|
||||
commands.emit(Command.OpenSetCover)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLayoutClicked(ctx: Id) {
|
||||
viewModelScope.launch {
|
||||
if (objectRestrictions.contains(ObjectRestriction.LAYOUT_CHANGE)) {
|
||||
_toasts.emit(NOT_ALLOWED)
|
||||
} else {
|
||||
commands.emit(Command.OpenSetLayout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRelationsClicked() {
|
||||
viewModelScope.launch {
|
||||
if (objectRestrictions.contains(ObjectRestriction.RELATIONS)) {
|
||||
_toasts.emit(NOT_ALLOWED)
|
||||
} else {
|
||||
commands.emit(Command.OpenSetRelations)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onHistoryClicked() {
|
||||
viewModelScope.launch { _toasts.emit(COMING_SOON_MSG) }
|
||||
}
|
||||
|
||||
override fun buildActions(
|
||||
ctx: Id,
|
||||
isArchived: Boolean,
|
||||
isFavorite: Boolean,
|
||||
isProfile: Boolean
|
||||
): List<ObjectAction> = buildList {
|
||||
if (isArchived) {
|
||||
add(ObjectAction.RESTORE)
|
||||
} else {
|
||||
add(ObjectAction.DELETE)
|
||||
}
|
||||
if (isFavorite) {
|
||||
add(ObjectAction.REMOVE_FROM_FAVOURITE)
|
||||
} else {
|
||||
add(ObjectAction.ADD_TO_FAVOURITE)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActionClicked(ctx: Id, action: ObjectAction) {
|
||||
when (action) {
|
||||
ObjectAction.DELETE -> {
|
||||
proceedWithUpdatingArchivedStatus(ctx = ctx, isArchived = true)
|
||||
}
|
||||
ObjectAction.RESTORE -> {
|
||||
proceedWithUpdatingArchivedStatus(ctx = ctx, isArchived = false)
|
||||
}
|
||||
ObjectAction.ADD_TO_FAVOURITE -> {
|
||||
proceedWithAddingToFavorites(ctx)
|
||||
}
|
||||
ObjectAction.REMOVE_FROM_FAVOURITE -> {
|
||||
proceedWithRemovingFromFavorites(ctx)
|
||||
}
|
||||
else -> {
|
||||
viewModelScope.launch { _toasts.emit(COMING_SOON_MSG) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
package com.anytypeio.anytype.presentation.objects.menu
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.anytypeio.anytype.core_models.Block.Fields
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.ObjectType
|
||||
import com.anytypeio.anytype.core_models.Relations
|
||||
import com.anytypeio.anytype.core_models.restrictions.ObjectRestriction
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
class ObjectMenuOptionsProviderImplTest {
|
||||
|
||||
private val objectId: String = "objectId"
|
||||
private val details = MutableStateFlow<Map<Id, Fields>>(mapOf())
|
||||
private val restrictions = MutableStateFlow<List<ObjectRestriction>>(emptyList())
|
||||
private val provider = ObjectMenuOptionsProviderImpl(details, restrictions)
|
||||
|
||||
@Test
|
||||
fun `when layout note - options are layout, relations, history`() {
|
||||
details.value = mapOf(
|
||||
objectId to Fields(map = mapOf(Relations.LAYOUT to ObjectType.Layout.NOTE.code.toDouble()))
|
||||
)
|
||||
val expected = ObjectMenuOptionsProvider.Options(
|
||||
hasIcon = false,
|
||||
hasCover = false,
|
||||
hasLayout = true,
|
||||
hasRelations = true,
|
||||
hasHistory = true
|
||||
|
||||
)
|
||||
|
||||
assertOptions(
|
||||
expected = expected
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when layout task - options are layout, relations, history`() {
|
||||
details.value = mapOf(
|
||||
objectId to Fields(map = mapOf(Relations.LAYOUT to ObjectType.Layout.TODO.code.toDouble()))
|
||||
)
|
||||
val expected = ObjectMenuOptionsProvider.Options(
|
||||
hasIcon = false,
|
||||
hasCover = true,
|
||||
hasLayout = true,
|
||||
hasRelations = true,
|
||||
hasHistory = true
|
||||
|
||||
)
|
||||
|
||||
assertOptions(
|
||||
expected = expected
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when layout basic - all options are visible`() {
|
||||
details.value = mapOf(
|
||||
objectId to Fields(map = mapOf(Relations.LAYOUT to ObjectType.Layout.BASIC.code.toDouble()))
|
||||
)
|
||||
|
||||
assertOptions(
|
||||
expected = ObjectMenuOptionsProvider.Options.ALL
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun `when layout null - all options are visible`() {
|
||||
details.value = mapOf(
|
||||
objectId to Fields(map = mapOf(Relations.LAYOUT to null))
|
||||
)
|
||||
|
||||
assertOptions(
|
||||
expected = ObjectMenuOptionsProvider.Options.ALL
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when restricts layout_change - layout options is invisible`() {
|
||||
details.value = mapOf(
|
||||
objectId to Fields(map = mapOf(Relations.LAYOUT to null))
|
||||
)
|
||||
restrictions.value = listOf(ObjectRestriction.LAYOUT_CHANGE)
|
||||
|
||||
assertOptions(
|
||||
expected = ObjectMenuOptionsProvider.Options.ALL.copy(hasLayout = false)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when object is Locked - show relations and history`() {
|
||||
details.value = mapOf(
|
||||
objectId to Fields(map = mapOf(Relations.LAYOUT to null))
|
||||
)
|
||||
|
||||
assertOptions(
|
||||
isLocked = true,
|
||||
expected = ObjectMenuOptionsProvider.Options(
|
||||
hasIcon = false,
|
||||
hasCover = false,
|
||||
hasLayout = false,
|
||||
hasRelations = true,
|
||||
hasHistory = true,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun assertOptions(
|
||||
expected: ObjectMenuOptionsProvider.Options,
|
||||
isLocked: Boolean = false,
|
||||
) {
|
||||
runTest {
|
||||
provider.provide(objectId, isLocked).test {
|
||||
assertEquals(
|
||||
expected = expected,
|
||||
actual = awaitItem()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue