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

DROID-1622 Set | Enhancement | View, menu (#312)

This commit is contained in:
Konstantin Ivanov 2023-08-27 18:09:24 +02:00 committed by GitHub
parent fd120cd894
commit b9a90778bd
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 741 additions and 2347 deletions

View file

@ -59,6 +59,7 @@ import com.anytypeio.anytype.presentation.sets.ObjectSetSession
import com.anytypeio.anytype.presentation.sets.ObjectSetViewModelFactory
import com.anytypeio.anytype.presentation.sets.state.DefaultObjectStateReducer
import com.anytypeio.anytype.presentation.sets.subscription.DefaultDataViewSubscription
import com.anytypeio.anytype.presentation.sets.viewer.ViewerDelegate
import com.anytypeio.anytype.presentation.templates.ObjectTypeTemplatesContainer
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.test_utils.MockDataFactory
@ -139,6 +140,9 @@ abstract class TestObjectSetSetup {
@Mock
lateinit var templatesContainer: ObjectTypeTemplatesContainer
@Mock
lateinit var viewerDelegate: ViewerDelegate
private lateinit var getTemplates: GetTemplates
private lateinit var getDefaultPageType: GetDefaultPageType
@ -259,7 +263,8 @@ abstract class TestObjectSetSetup {
updateDataViewViewer = updateDataViewViewer,
templatesContainer = templatesContainer,
setObjectListIsArchived = setObjectListIsArchived,
duplicateObjects = duplicateObjects
duplicateObjects = duplicateObjects,
viewerDelegate = viewerDelegate
)
}

View file

@ -9,13 +9,11 @@ import com.anytypeio.anytype.di.feature.AddObjectRelationValueModule
import com.anytypeio.anytype.di.feature.AuthModule
import com.anytypeio.anytype.di.feature.CreateAccountModule
import com.anytypeio.anytype.di.feature.CreateBookmarkModule
import com.anytypeio.anytype.di.feature.CreateDataViewViewerModule
import com.anytypeio.anytype.di.feature.CreateObjectModule
import com.anytypeio.anytype.di.feature.DaggerBacklinkOrAddToObjectComponent
import com.anytypeio.anytype.di.feature.DaggerSplashComponent
import com.anytypeio.anytype.di.feature.DataViewRelationValueModule
import com.anytypeio.anytype.di.feature.DebugSettingsModule
import com.anytypeio.anytype.di.feature.EditDataViewViewerModule
import com.anytypeio.anytype.di.feature.EditorSessionModule
import com.anytypeio.anytype.di.feature.EditorUseCaseModule
import com.anytypeio.anytype.di.feature.KeychainLoginModule
@ -23,7 +21,6 @@ import com.anytypeio.anytype.di.feature.KeychainPhraseModule
import com.anytypeio.anytype.di.feature.LinkToObjectModule
import com.anytypeio.anytype.di.feature.LinkToObjectOrWebModule
import com.anytypeio.anytype.di.feature.MainEntryModule
import com.anytypeio.anytype.di.feature.ManageViewerModule
import com.anytypeio.anytype.di.feature.ModifyViewerSortModule
import com.anytypeio.anytype.di.feature.MoveToModule
import com.anytypeio.anytype.di.feature.ObjectAppearanceIconModule
@ -451,22 +448,6 @@ class ComponentManager(
.build()
}
val createDataViewViewerComponent = DependentComponentMap { ctx ->
objectSetComponent
.get(ctx)
.createDataViewViewerSubComponent()
.module(CreateDataViewViewerModule)
.build()
}
val editDataViewViewerComponent = DependentComponentMap { ctx ->
objectSetComponent
.get(ctx)
.editDataViewViewerComponent()
.module(EditDataViewViewerModule)
.build()
}
val dataViewRelationValueComponent = DependentComponentMap { ctx ->
objectSetComponent
.get(ctx)
@ -539,14 +520,6 @@ class ComponentManager(
.build()
}
val manageViewerComponent = DependentComponentMap { ctx ->
objectSetComponent
.get(ctx)
.manageViewerComponent()
.module(ManageViewerModule)
.build()
}
val objectsSetSettingsComponent = DependentComponentMap { ctx ->
objectSetComponent
.get(ctx)

View file

@ -1,53 +0,0 @@
package com.anytypeio.anytype.di.feature;
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_utils.di.scope.PerModal
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.dataview.interactor.AddDataViewViewer
import com.anytypeio.anytype.presentation.sets.CreateDataViewViewerViewModel
import com.anytypeio.anytype.presentation.sets.state.ObjectState
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.ui.sets.modals.CreateDataViewViewerFragment
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
import kotlinx.coroutines.flow.MutableStateFlow
@Subcomponent(modules = [CreateDataViewViewerModule::class])
@PerModal
interface CreateDataViewViewerSubComponent {
@Subcomponent.Builder
interface Builder {
fun module(module: CreateDataViewViewerModule): Builder
fun build(): CreateDataViewViewerSubComponent
}
fun inject(fragment: CreateDataViewViewerFragment)
}
@Module
object CreateDataViewViewerModule {
@JvmStatic
@Provides
@PerModal
fun provideCreateDataViewViewerViewModelFactory(
dispatcher: Dispatcher<Payload>,
addDataViewViewer: AddDataViewViewer,
analytics: Analytics,
objectState: MutableStateFlow<ObjectState>
): CreateDataViewViewerViewModel.Factory = CreateDataViewViewerViewModel.Factory(
dispatcher = dispatcher,
addDataViewViewer = addDataViewViewer,
analytics = analytics,
objectState = objectState
)
@JvmStatic
@Provides
@PerModal
fun provideAddDataViewViewerUseCase(
repo: BlockRepository
): AddDataViewViewer = AddDataViewViewer(repo = repo)
}

View file

@ -1,80 +0,0 @@
package com.anytypeio.anytype.di.feature;
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_utils.di.scope.PerModal
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.dataview.interactor.*
import com.anytypeio.anytype.presentation.sets.EditDataViewViewerViewModel
import com.anytypeio.anytype.presentation.sets.ObjectSetPaginator
import com.anytypeio.anytype.presentation.sets.ObjectSetSession
import com.anytypeio.anytype.presentation.sets.state.ObjectState
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.ui.sets.modals.EditDataViewViewerFragment
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@Subcomponent(modules = [EditDataViewViewerModule::class])
@PerModal
interface EditDataViewViewerSubComponent {
@Subcomponent.Builder
interface Builder {
fun module(module: EditDataViewViewerModule): Builder
fun build(): EditDataViewViewerSubComponent
}
fun inject(fragment: EditDataViewViewerFragment)
}
@Module
object EditDataViewViewerModule {
@JvmStatic
@Provides
@PerModal
fun provideEditDataViewViewerViewModelFactory(
renameDataViewViewer: RenameDataViewViewer,
deleteDataViewViewer: DeleteDataViewViewer,
duplicateDataViewViewer: DuplicateDataViewViewer,
updateDataViewViewer: UpdateDataViewViewer,
dispatcher: Dispatcher<Payload>,
state: MutableStateFlow<ObjectState>,
objectSetSession: ObjectSetSession,
paginator: ObjectSetPaginator,
analytics: Analytics
): EditDataViewViewerViewModel.Factory = EditDataViewViewerViewModel.Factory(
renameDataViewViewer = renameDataViewViewer,
deleteDataViewViewer = deleteDataViewViewer,
duplicateDataViewViewer = duplicateDataViewViewer,
updateDataViewViewer = updateDataViewViewer,
dispatcher = dispatcher,
objectState = state,
objectSetSession = objectSetSession,
paginator = paginator,
analytics = analytics
)
@JvmStatic
@Provides
@PerModal
fun provideRenameDataViewViewerUseCase(
repo: BlockRepository
): RenameDataViewViewer = RenameDataViewViewer(repo = repo)
@JvmStatic
@Provides
@PerModal
fun provideDuplicateDataViewViewerUseCase(
repo: BlockRepository
): DuplicateDataViewViewer = DuplicateDataViewViewer(repo = repo)
@JvmStatic
@Provides
@PerModal
fun provideDeleteDataViewViewerUseCase(
repo: BlockRepository
): DeleteDataViewViewer = DeleteDataViewViewer(repo = repo)
}

View file

@ -1,71 +0,0 @@
package com.anytypeio.anytype.di.feature;
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_utils.di.scope.PerModal
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.dataview.interactor.DeleteDataViewViewer
import com.anytypeio.anytype.domain.dataview.interactor.SetDataViewViewerPosition
import com.anytypeio.anytype.presentation.sets.ManageViewerViewModel
import com.anytypeio.anytype.presentation.sets.ObjectSetSession
import com.anytypeio.anytype.presentation.sets.state.ObjectState
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.ui.sets.modals.ManageViewerFragment
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
import kotlinx.coroutines.flow.MutableStateFlow
@Subcomponent(modules = [ManageViewerModule::class])
@PerModal
interface ManageViewerSubComponent {
@Subcomponent.Builder
interface Builder {
fun module(module: ManageViewerModule): Builder
fun build(): ManageViewerSubComponent
}
fun inject(fragment: ManageViewerFragment)
}
@Module
object ManageViewerModule {
@JvmStatic
@Provides
@PerModal
fun provideManageViewerViewModelFactory(
state: MutableStateFlow<ObjectState>,
session: ObjectSetSession,
dispatcher: Dispatcher<Payload>,
analytics: Analytics,
deleteDataViewViewer: DeleteDataViewViewer,
setDataViewViewerPosition: SetDataViewViewerPosition
): ManageViewerViewModel.Factory = ManageViewerViewModel.Factory(
objectState = state,
session = session,
dispatcher = dispatcher,
analytics = analytics,
deleteDataViewViewer = deleteDataViewViewer,
setDataViewViewerPosition = setDataViewViewerPosition
)
@JvmStatic
@Provides
@PerModal
fun provideDeleteDataViewViewerUseCase(
repo: BlockRepository
): DeleteDataViewViewer = DeleteDataViewViewer(repo = repo)
@JvmStatic
@Provides
@PerModal
fun provideSetDataViewViewerPosition(
repo: BlockRepository,
dispatchers: AppCoroutineDispatchers
): SetDataViewViewerPosition = SetDataViewViewerPosition(
repo = repo,
dispatchers = dispatchers
)
}

View file

@ -23,7 +23,12 @@ import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.collections.AddObjectToCollection
import com.anytypeio.anytype.domain.config.UserSettingsRepository
import com.anytypeio.anytype.domain.cover.SetDocCoverImage
import com.anytypeio.anytype.domain.dataview.interactor.AddDataViewViewer
import com.anytypeio.anytype.domain.dataview.interactor.CreateDataViewObject
import com.anytypeio.anytype.domain.dataview.interactor.DeleteDataViewViewer
import com.anytypeio.anytype.domain.dataview.interactor.DuplicateDataViewViewer
import com.anytypeio.anytype.domain.dataview.interactor.RenameDataViewViewer
import com.anytypeio.anytype.domain.dataview.interactor.SetDataViewViewerPosition
import com.anytypeio.anytype.domain.dataview.interactor.UpdateDataViewViewer
import com.anytypeio.anytype.domain.event.interactor.EventChannel
import com.anytypeio.anytype.domain.event.interactor.InterceptEvents
@ -81,6 +86,8 @@ import com.anytypeio.anytype.presentation.sets.state.ObjectState
import com.anytypeio.anytype.presentation.sets.state.ObjectStateReducer
import com.anytypeio.anytype.presentation.sets.subscription.DataViewSubscription
import com.anytypeio.anytype.presentation.sets.subscription.DefaultDataViewSubscription
import com.anytypeio.anytype.presentation.sets.viewer.DefaultViewerDelegate
import com.anytypeio.anytype.presentation.sets.viewer.ViewerDelegate
import com.anytypeio.anytype.presentation.templates.ObjectTypeTemplatesContainer
import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory
import com.anytypeio.anytype.presentation.util.DefaultCopyFileToCacheDirectory
@ -110,13 +117,10 @@ interface ObjectSetSubComponent {
fun objectSetRecordComponent(): ObjectSetRecordSubComponent.Builder
fun objectSetCreateBookmarkRecordComponent(): ObjectSetCreateBookmarkRecordSubComponent.Builder
fun viewerFilterBySubComponent(): ViewerFilterSubComponent.Builder
fun createDataViewViewerSubComponent(): CreateDataViewViewerSubComponent.Builder
fun editDataViewViewerComponent(): EditDataViewViewerSubComponent.Builder
fun dataViewObjectRelationValueComponent(): DataViewObjectRelationValueSubComponent.Builder
fun setOrCollectionRelationValueComponent() : SetOrCollectionRelationValueSubComponent.Builder
fun manageViewerComponent(): ManageViewerSubComponent.Builder
fun objectSetSettingsComponent(): ObjectSetSettingsSubComponent.Builder
fun viewerCardSizeSelectComponent(): ViewerCardSizeSelectSubcomponent.Builder
fun viewerImagePreviewSelectComponent(): ViewerImagePreviewSelectSubcomponent.Builder
@ -214,7 +218,8 @@ object ObjectSetModule {
updateDataViewViewer: UpdateDataViewViewer,
duplicateObjects: DuplicateObjects,
templatesContainer: ObjectTypeTemplatesContainer,
setObjectListIsArchived: SetObjectListIsArchived
setObjectListIsArchived: SetObjectListIsArchived,
viewerDelegate: ViewerDelegate
): ObjectSetViewModelFactory = ObjectSetViewModelFactory(
openObjectSet = openObjectSet,
closeBlock = closeBlock,
@ -249,7 +254,8 @@ object ObjectSetModule {
updateDataViewViewer = updateDataViewViewer,
duplicateObjects = duplicateObjects,
templatesContainer = templatesContainer,
setObjectListIsArchived = setObjectListIsArchived
setObjectListIsArchived = setObjectListIsArchived,
viewerDelegate = viewerDelegate
)
@JvmStatic
@ -630,4 +636,67 @@ object ObjectSetModule {
defaultProvider: DefaultCoverImageHashProvider
): CoverImageHashProvider
}
@JvmStatic
@Provides
@PerScreen
fun provideAddDataViewViewerUseCase(
repo: BlockRepository,
dispatchers: AppCoroutineDispatchers
): AddDataViewViewer = AddDataViewViewer(repo = repo, dispatchers = dispatchers)
@JvmStatic
@Provides
@PerScreen
fun provideRenameDataViewViewerUseCase(
repo: BlockRepository,
dispatchers: AppCoroutineDispatchers
): RenameDataViewViewer = RenameDataViewViewer(repo = repo, dispatchers = dispatchers)
@JvmStatic
@Provides
@PerScreen
fun provideDuplicateDataViewViewerUseCase(
repo: BlockRepository,
dispatchers: AppCoroutineDispatchers
): DuplicateDataViewViewer = DuplicateDataViewViewer(repo = repo, dispatchers = dispatchers)
@JvmStatic
@Provides
@PerScreen
fun provideDeleteDataViewViewerUseCase(
repo: BlockRepository,
dispatchers: AppCoroutineDispatchers
): DeleteDataViewViewer = DeleteDataViewViewer(repo = repo, dispatchers = dispatchers)
@JvmStatic
@Provides
@PerScreen
fun provideSetDataViewViewerPositionUseCase(
repo: BlockRepository,
dispatchers: AppCoroutineDispatchers
): SetDataViewViewerPosition = SetDataViewViewerPosition(repo = repo, dispatchers = dispatchers)
@JvmStatic
@Provides
@PerScreen
fun provideViewerDelegate(
session: ObjectSetSession,
addDataViewViewer: AddDataViewViewer,
renameDataViewViewer: RenameDataViewViewer,
duplicateDataViewViewer: DuplicateDataViewViewer,
deleteDataViewViewer: DeleteDataViewViewer,
setDataViewViewerPosition: SetDataViewViewerPosition,
analytics: Analytics,
dispatcher: Dispatcher<Payload>
): ViewerDelegate = DefaultViewerDelegate(
session = session,
addDataViewViewer = addDataViewViewer,
renameDataViewViewer = renameDataViewViewer,
duplicateDataViewViewer = duplicateDataViewViewer,
deleteDataViewViewer = deleteDataViewViewer,
setDataViewViewerPosition = setDataViewViewerPosition,
analytics = analytics,
dispatcher = dispatcher
)
}

View file

@ -52,6 +52,7 @@ import com.anytypeio.anytype.core_ui.views.ButtonPrimarySmallIcon
import com.anytypeio.anytype.core_ui.widgets.FeaturedRelationGroupWidget
import com.anytypeio.anytype.core_ui.widgets.ObjectTypeTemplatesWidget
import com.anytypeio.anytype.core_ui.widgets.StatusBadgeWidget
import com.anytypeio.anytype.core_ui.widgets.dv.ViewersWidget
import com.anytypeio.anytype.core_ui.widgets.text.TextInputWidget
import com.anytypeio.anytype.core_ui.widgets.toolbar.DataViewInfo
import com.anytypeio.anytype.core_utils.OnSwipeListener
@ -73,6 +74,7 @@ import com.anytypeio.anytype.databinding.FragmentObjectSetBinding
import com.anytypeio.anytype.di.common.componentManager
import com.anytypeio.anytype.presentation.editor.cover.CoverColor
import com.anytypeio.anytype.presentation.editor.cover.CoverGradient
import com.anytypeio.anytype.presentation.sets.ViewersWidgetUi
import com.anytypeio.anytype.presentation.sets.DataViewViewState
import com.anytypeio.anytype.presentation.sets.ObjectSetCommand
import com.anytypeio.anytype.presentation.sets.ObjectSetViewModel
@ -93,9 +95,6 @@ import com.anytypeio.anytype.ui.relations.RelationTextValueFragment
import com.anytypeio.anytype.ui.relations.RelationTextValueFragment.TextValueEditReceiver
import com.anytypeio.anytype.ui.relations.RelationValueBaseFragment
import com.anytypeio.anytype.ui.relations.RelationValueDVFragment
import com.anytypeio.anytype.ui.sets.modals.CreateDataViewViewerFragment
import com.anytypeio.anytype.ui.sets.modals.EditDataViewViewerFragment
import com.anytypeio.anytype.ui.sets.modals.ManageViewerFragment
import com.anytypeio.anytype.ui.sets.modals.ObjectSetSettingsFragment
import com.anytypeio.anytype.ui.sets.modals.SetObjectCreateRecordFragmentBase
import com.anytypeio.anytype.ui.sets.modals.sort.ViewerSortFragment
@ -340,6 +339,16 @@ open class ObjectSetFragment :
}
observeSelectingTemplate()
binding.viewersWidget.apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
ViewersWidget(
state = vm.viewersWidgetState.collectAsStateWithLifecycle().value,
action = vm::onViewersWidgetAction
)
}
}
}
private fun setupWindowInsetAnimation() {
@ -877,24 +886,6 @@ open class ObjectSetFragment :
)
)
}
is ObjectSetCommand.Modal.CreateViewer -> {
val fr = CreateDataViewViewerFragment.new(
ctx = command.ctx,
target = command.target
)
fr.showChildFragment(EMPTY_TAG)
}
is ObjectSetCommand.Modal.EditDataViewViewer -> {
val fr = EditDataViewViewerFragment.new(
ctx = command.ctx,
viewer = command.viewer
)
fr.showChildFragment(EMPTY_TAG)
}
is ObjectSetCommand.Modal.ManageViewer -> {
val fr = ManageViewerFragment.new(ctx = command.ctx, dv = command.dataview)
fr.showChildFragment(EMPTY_TAG)
}
is ObjectSetCommand.Modal.OpenSettings -> {
val fr = ObjectSetSettingsFragment.new(
ctx = command.ctx,
@ -1109,6 +1100,9 @@ open class ObjectSetFragment :
vm.templatesWidgetState.value.showWidget -> {
vm.onDismissTemplatesWidget()
}
vm.viewersWidgetState.value.showWidget -> {
vm.onViewersWidgetAction(ViewersWidgetUi.Action.Dismiss)
}
else -> {
vm.onSystemBackPressed()
}

View file

@ -1,107 +0,0 @@
package com.anytypeio.anytype.ui.sets.modals
import android.os.Bundle
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 com.anytypeio.anytype.core_ui.reactive.clicks
import com.anytypeio.anytype.core_utils.ext.*
import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetFragment
import com.anytypeio.anytype.databinding.FragmentCreateDataViewViewerBinding
import com.anytypeio.anytype.di.common.componentManager
import com.anytypeio.anytype.presentation.sets.CreateDataViewViewerViewModel
import javax.inject.Inject
class CreateDataViewViewerFragment : BaseBottomSheetFragment<FragmentCreateDataViewViewerBinding>() {
val ctx get() = arg<String>(CTX_KEY)
val target get() = arg<String>(TARGET_KEY)
@Inject
lateinit var factory: CreateDataViewViewerViewModel.Factory
private val vm: CreateDataViewViewerViewModel by viewModels { factory }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
with(lifecycleScope) {
subscribe(binding.btnCreateViewer.clicks()) {
vm.onAddViewer(
name = binding.viewerNameInput.text.toString(),
ctx = ctx,
target = target
)
}
subscribe(binding.gridContainer.clicks()) { vm.onGridClicked() }
subscribe(binding.galleryContainer.clicks()) { vm.onGalleryClicked() }
subscribe(binding.listContainer.clicks()) { vm.onListClicked() }
}
}
override fun onStart() {
with(lifecycleScope) {
jobs += subscribe(vm.state) { render(it) }
}
super.onStart()
}
private fun render(state: CreateDataViewViewerViewModel.ViewState) {
when (state) {
CreateDataViewViewerViewModel.ViewState.Init -> {
binding.isListChosen.invisible()
binding.isTableChosen.visible()
binding.isGalleryChosen.invisible()
}
CreateDataViewViewerViewModel.ViewState.Completed -> {
dismiss()
}
is CreateDataViewViewerViewModel.ViewState.Error -> {
toast(state.msg)
}
CreateDataViewViewerViewModel.ViewState.Gallery -> {
binding.isListChosen.invisible()
binding.isTableChosen.invisible()
binding.isGalleryChosen.visible()
}
CreateDataViewViewerViewModel.ViewState.Grid -> {
binding.isListChosen.invisible()
binding.isTableChosen.visible()
binding.isGalleryChosen.invisible()
}
CreateDataViewViewerViewModel.ViewState.List -> {
binding.isListChosen.visible()
binding.isTableChosen.invisible()
binding.isGalleryChosen.invisible()
}
}
}
override fun injectDependencies() {
componentManager().createDataViewViewerComponent.get(ctx).inject(this)
}
override fun releaseDependencies() {
componentManager().createDataViewViewerComponent.release(ctx)
}
override fun inflateBinding(
inflater: LayoutInflater,
container: ViewGroup?
): FragmentCreateDataViewViewerBinding = FragmentCreateDataViewViewerBinding.inflate(
inflater, container, false
)
companion object {
fun new(ctx: String, target: String) = CreateDataViewViewerFragment().apply {
arguments = bundleOf(
CTX_KEY to ctx, TARGET_KEY to target
)
}
private const val CTX_KEY = "arg.create-data-view-viewer.context"
private const val TARGET_KEY = "arg.create-data-view-viewer.target"
}
}

View file

@ -1,164 +0,0 @@
package com.anytypeio.anytype.ui.sets.modals
import android.os.Bundle
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 com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_ui.menu.DataViewEditViewPopupMenu
import com.anytypeio.anytype.core_ui.reactive.clicks
import com.anytypeio.anytype.core_ui.reactive.textChanges
import com.anytypeio.anytype.core_utils.ext.arg
import com.anytypeio.anytype.core_utils.ext.gone
import com.anytypeio.anytype.core_utils.ext.hideKeyboard
import com.anytypeio.anytype.core_utils.ext.invisible
import com.anytypeio.anytype.core_utils.ext.subscribe
import com.anytypeio.anytype.core_utils.ext.toast
import com.anytypeio.anytype.core_utils.ext.visible
import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetFragment
import com.anytypeio.anytype.databinding.FragmentEditDataViewViewerBinding
import com.anytypeio.anytype.di.common.componentManager
import com.anytypeio.anytype.presentation.sets.EditDataViewViewerViewModel
import javax.inject.Inject
class
EditDataViewViewerFragment : BaseBottomSheetFragment<FragmentEditDataViewViewerBinding>() {
private val ctx: Id get() = arg(CTX_KEY)
private val viewer: Id get() = arg(VIEWER_KEY)
@Inject
lateinit var factory: EditDataViewViewerViewModel.Factory
private val vm: EditDataViewViewerViewModel by viewModels { factory }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
with(lifecycleScope) {
subscribe(binding.viewerNameInput.textChanges()) { name ->
vm.onViewerNameChanged(name = name.toString())
}
subscribe(binding.btnDone.clicks()) {
vm.onDoneClicked(ctx, viewer)
}
subscribe(binding.threeDotsButton.clicks()) { vm.onMenuClicked(viewer) }
subscribe(binding.gridContainer.clicks()) { vm.onGridClicked() }
subscribe(binding.galleryContainer.clicks()) { vm.onGalleryClicked() }
subscribe(binding.listContainer.clicks()) { vm.onListClicked() }
}
}
override fun onStart() {
with(lifecycleScope) {
jobs += subscribe(vm.viewState) { render(it) }
jobs += subscribe(vm.isDismissed) { isDismissed ->
if (isDismissed) {
binding.viewerNameInput.apply {
clearFocus()
hideKeyboard()
}
dismiss()
}
}
jobs += subscribe(vm.isLoading) { isLoading ->
if (isLoading) binding.progressBar.visible() else binding.progressBar.gone()
}
jobs += subscribe(vm.toasts) { toast(it) }
jobs += subscribe(vm.popupCommands) { cmd ->
DataViewEditViewPopupMenu(
requireContext(),
binding.threeDotsButton,
cmd.isDeletionAllowed
).apply {
setOnMenuItemClickListener { item ->
when (item.itemId) {
R.id.duplicate -> vm.onDuplicateClicked(ctx = ctx, viewer = viewer)
R.id.delete -> vm.onDeleteClicked(ctx = ctx, viewer = viewer)
}
true
}
}.show()
}
}
super.onStart()
vm.onStart(viewer)
}
private fun render(state: EditDataViewViewerViewModel.ViewState) {
when (state) {
EditDataViewViewerViewModel.ViewState.Init -> {
with(binding) {
viewerNameInput.text = null
isListChosen.invisible()
isTableChosen.invisible()
isGalleryChosen.invisible()
}
}
is EditDataViewViewerViewModel.ViewState.Name -> {
binding.viewerNameInput.setText(state.name)
}
EditDataViewViewerViewModel.ViewState.Completed -> {
dismiss()
}
is EditDataViewViewerViewModel.ViewState.Error -> {
toast(state.msg)
}
EditDataViewViewerViewModel.ViewState.Gallery -> {
with(binding) {
isListChosen.invisible()
isTableChosen.invisible()
isGalleryChosen.visible()
}
}
EditDataViewViewerViewModel.ViewState.Grid -> {
with(binding) {
isListChosen.invisible()
isTableChosen.visible()
isGalleryChosen.invisible()
}
}
EditDataViewViewerViewModel.ViewState.List -> {
with(binding) {
isListChosen.visible()
isTableChosen.invisible()
isGalleryChosen.invisible()
}
}
EditDataViewViewerViewModel.ViewState.Kanban -> {}
}
}
override fun injectDependencies() {
componentManager().editDataViewViewerComponent.get(ctx).inject(this)
}
override fun releaseDependencies() {
componentManager().editDataViewViewerComponent.release(ctx)
}
override fun inflateBinding(
inflater: LayoutInflater,
container: ViewGroup?
): FragmentEditDataViewViewerBinding = FragmentEditDataViewViewerBinding.inflate(
inflater, container, false
)
companion object {
const val CTX_KEY = "arg.edit-data-view-viewer.ctx"
const val VIEWER_KEY = "arg.edit-data-view-viewer.viewer"
fun new(
ctx: Id,
viewer: Id
): EditDataViewViewerFragment = EditDataViewViewerFragment().apply {
arguments = bundleOf(
CTX_KEY to ctx,
VIEWER_KEY to viewer
)
}
}
}

View file

@ -1,168 +0,0 @@
package com.anytypeio.anytype.ui.sets.modals
import android.os.Bundle
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.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_ui.features.sets.ManageViewerDoneAdapter
import com.anytypeio.anytype.core_ui.features.sets.ManageViewerEditAdapter
import com.anytypeio.anytype.core_ui.reactive.clicks
import com.anytypeio.anytype.core_ui.tools.DefaultDragAndDropBehavior
import com.anytypeio.anytype.core_utils.ext.*
import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetFragment
import com.anytypeio.anytype.core_utils.ui.OnStartDragListener
import com.anytypeio.anytype.databinding.FragmentManageViewerBinding
import com.anytypeio.anytype.di.common.componentManager
import com.anytypeio.anytype.presentation.sets.ManageViewerViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject
class ManageViewerFragment : BaseBottomSheetFragment<FragmentManageViewerBinding>(),
OnStartDragListener {
private val manageViewerAdapter by lazy {
ManageViewerDoneAdapter(
onViewerClicked = { view -> vm.onViewerClicked(ctx = ctx, view = view) }
)
}
private val manageViewerEditAdapter by lazy {
ManageViewerEditAdapter(
onDragListener = this,
onButtonMoreClicked = vm::onViewerActionClicked,
onDeleteView = {
vm.onDeleteView(
ctx = ctx,
dv = dv,
view = it
)
},
onDeleteActiveView = { toast(R.string.toast_active_view_delete) }
)
}
private val ctx: Id get() = arg(CTX_KEY)
private val dv: Id get() = arg(DATA_VIEW_KEY)
@Inject
lateinit var factory: ManageViewerViewModel.Factory
private val vm: ManageViewerViewModel by viewModels { factory }
private val dndItemTouchHelper: ItemTouchHelper by lazy { ItemTouchHelper(dndBehavior) }
private val dndBehavior by lazy {
DefaultDragAndDropBehavior(
onItemMoved = { from, to -> manageViewerEditAdapter.onItemMove(from, to) },
onItemDropped = { newPosition ->
vm.onOrderChanged(
ctx = ctx,
dv = dv,
newOrder = manageViewerEditAdapter.order,
newPosition = newPosition
)
}
)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.dataViewViewerRecycler.apply {
layoutManager = LinearLayoutManager(context)
}
with(lifecycleScope) {
subscribe(binding.btnEditViewers.clicks()) { vm.onButtonEditClicked() }
subscribe(binding.btnAddNewViewer.clicks()) { vm.onButtonAddClicked() }
}
}
override fun onStartDrag(viewHolder: RecyclerView.ViewHolder) {
}
override fun onStart() {
lifecycleScope.launch {
jobs += subscribe(vm.commands) { observe(it) }
jobs += subscribe(vm.toasts) { toast(it) }
jobs += subscribe(vm.views) {
manageViewerAdapter.update(it)
manageViewerEditAdapter.update(it)
}
jobs += subscribe(vm.isDismissed) { isDismissed ->
if (isDismissed) dismiss()
}
jobs += subscribe(vm.isEditEnabled) { isEditEnabled ->
if (isEditEnabled) {
with(binding) {
btnEditViewers.setText(R.string.done)
btnAddNewViewer.invisible()
dataViewViewerRecycler.apply {
adapter = manageViewerEditAdapter
dndItemTouchHelper.attachToRecyclerView(this)
}
}
} else {
with(binding) {
btnEditViewers.setText(R.string.edit)
btnAddNewViewer.visible()
dataViewViewerRecycler.apply {
adapter = manageViewerAdapter
dndItemTouchHelper.attachToRecyclerView(null)
}
}
}
}
}
super.onStart()
}
private fun observe(command: ManageViewerViewModel.Command) {
when (command) {
is ManageViewerViewModel.Command.OpenEditScreen -> {
val dialog = EditDataViewViewerFragment.new(
ctx = ctx,
viewer = command.id
)
dialog.show(parentFragmentManager, null)
}
ManageViewerViewModel.Command.OpenCreateScreen -> {
val dialog = CreateDataViewViewerFragment.new(
ctx = ctx,
target = dv
)
dialog.show(parentFragmentManager, null)
}
}
}
override fun injectDependencies() {
componentManager().manageViewerComponent.get(ctx).inject(this)
}
override fun releaseDependencies() {
componentManager().manageViewerComponent.release(ctx)
}
override fun inflateBinding(
inflater: LayoutInflater,
container: ViewGroup?
): FragmentManageViewerBinding = FragmentManageViewerBinding.inflate(
inflater, container, false
)
companion object {
fun new(ctx: Id, dv: Id): ManageViewerFragment = ManageViewerFragment().apply {
arguments = bundleOf(CTX_KEY to ctx, DATA_VIEW_KEY to dv)
}
const val CTX_KEY = "arg.manage-data-view-viewer.ctx"
const val DATA_VIEW_KEY = "arg.manage-data-view-viewer.dataview"
}
}

View file

@ -1,197 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--typography, buttons 05.04-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:paddingStart="20dp"
android:paddingEnd="20dp">
<View
android:id="@+id/dragger"
android:layout_width="48dp"
android:layout_height="4dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="6dp"/>
<TextView
style="@style/TextView.UXStyle.Titles.1"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_marginTop="18dp"
android:gravity="center"
android:text="@string/new_view" />
<TextView
style="@style/TextView.UXStyle.Captions.1.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/text_secondary"
android:layout_marginTop="23dp"
android:text="@string/name" />
<EditText
android:id="@+id/viewerNameInput"
style="@style/TextView.ContentStyle.Headline.Heading"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:background="@null"
android:hint="@string/new_view"
android:singleLine="true"
android:maxLines="1"
android:imeOptions="actionDone"
android:textColorHint="@color/text_secondary"
tools:text="View"/>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginTop="10dp"
android:background="@color/shape_primary" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/TextView.UXStyle.Captions.1.Regular"
android:textColor="@color/text_secondary"
android:layout_marginTop="27dp"
android:text="@string/viewer_as" />
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginTop="8dp"
android:background="@color/shape_primary" />
<FrameLayout
android:id="@+id/gridContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<FrameLayout
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginTop="12dp"
android:layout_marginBottom="12dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/ic_create_viewer_grid" />
</FrameLayout>
<TextView
style="@style/TextView.UXStyle.Body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="44dp"
android:text="@string/viewer_table" />
<ImageView
android:id="@+id/isTableChosen"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:src="@drawable/ic_viewer_chosen" />
</FrameLayout>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="@color/shape_primary" />
<FrameLayout
android:id="@+id/galleryContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<FrameLayout
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginTop="12dp"
android:layout_marginBottom="12dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/ic_create_viewer_gallery" />
</FrameLayout>
<TextView
style="@style/TextView.UXStyle.Body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="44dp"
android:text="@string/viewer_gallery" />
<ImageView
android:id="@+id/isGalleryChosen"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:src="@drawable/ic_viewer_chosen"
android:visibility="visible" />
</FrameLayout>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="@color/shape_primary" />
<FrameLayout
android:id="@+id/listContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<FrameLayout
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginTop="12dp"
android:layout_marginBottom="12dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/ic_create_viewer_list" />
</FrameLayout>
<TextView
style="@style/TextView.UXStyle.Body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="44dp"
android:text="@string/viewer_list" />
<ImageView
android:id="@+id/isListChosen"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:src="@drawable/ic_viewer_chosen"
android:visibility="visible" />
</FrameLayout>
<com.anytypeio.anytype.core_ui.views.ButtonPrimaryLarge
android:id="@+id/btnCreateViewer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:layout_marginBottom="20dp"
android:text="@string/create" />
</LinearLayout>

View file

@ -1,281 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--typography, buttons 05.04-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="20dp"
android:paddingEnd="20dp">
<View
android:id="@+id/dragger"
android:layout_width="48dp"
android:layout_height="4dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="6dp" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="18dp">
<TextView
style="@style/TextView.UXStyle.Titles.1"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:text="@string/edit_view" />
<ImageView
android:id="@+id/threeDotsButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:src="@drawable/ic_action_more" />
</FrameLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/TextView.UXStyle.Captions.1.Regular"
android:layout_marginTop="23dp"
android:text="@string/name"
android:textColor="@color/text_secondary" />
<EditText
android:id="@+id/viewerNameInput"
style="@style/TextView.ContentStyle.Headline.Heading"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:background="@null"
android:hint="@string/untitled"
android:imeOptions="actionDone"
android:maxLines="1"
android:singleLine="true"
android:textColorHint="@color/text_secondary" />
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginTop="10dp"
android:background="@color/shape_primary" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/TextView.UXStyle.Captions.1.Regular"
android:layout_marginTop="27dp"
android:text="@string/viewer_as"
android:textColor="@color/text_secondary" />
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginTop="8dp"
android:background="@color/shape_primary" />
<FrameLayout
android:id="@+id/gridContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<FrameLayout
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginTop="12dp"
android:layout_marginBottom="12dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/ic_create_viewer_grid" />
</FrameLayout>
<TextView
style="@style/TextView.UXStyle.Body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="44dp"
android:text="@string/viewer_table" />
<ImageView
android:id="@+id/isTableChosen"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:src="@drawable/ic_viewer_chosen" />
</FrameLayout>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="@color/shape_primary" />
<FrameLayout
android:id="@+id/galleryContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<FrameLayout
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginTop="12dp"
android:layout_marginBottom="12dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/ic_create_viewer_gallery" />
</FrameLayout>
<TextView
style="@style/TextView.UXStyle.Body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="44dp"
android:text="@string/viewer_gallery" />
<ImageView
android:id="@+id/isGalleryChosen"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:src="@drawable/ic_viewer_chosen"
android:visibility="visible" />
</FrameLayout>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="@color/shape_primary" />
<FrameLayout
android:id="@+id/listContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<FrameLayout
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginTop="12dp"
android:layout_marginBottom="12dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/ic_create_viewer_list" />
</FrameLayout>
<TextView
style="@style/TextView.UXStyle.Body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="44dp"
android:text="@string/viewer_list" />
<ImageView
android:id="@+id/isListChosen"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:src="@drawable/ic_viewer_chosen"
android:visibility="visible" />
</FrameLayout>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="@color/shape_primary" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<FrameLayout
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginTop="12dp"
android:layout_marginBottom="12dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/ic_create_viewer_kanban" />
</FrameLayout>
<TextView
style="@style/TextView.UXStyle.Body"
android:textColor="@color/text_tertiary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="44dp"
android:text="@string/viewer_kanban" />
<ImageView
android:id="@+id/isKanbanChosen"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:src="@drawable/ic_viewer_chosen"
android:visibility="invisible" />
<TextView
style="@style/TextView.UXStyle.Captions.1.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:text="@string/soon"
android:textColor="@color/text_tertiary"
android:textAllCaps="true" />
</FrameLayout>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="@color/shape_primary" />
<FrameLayout
android:id="@+id/btnDoneContainer"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginTop="12dp"
android:layout_marginBottom="20dp">
<com.anytypeio.anytype.core_ui.views.ButtonPrimaryLarge
android:id="@+id/btnDone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/done" />
<ProgressBar
android:visibility="gone"
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleSmall"
android:indeterminateTint="@color/white"
android:layout_marginEnd="16dp"
android:layout_gravity="center_vertical|end"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</FrameLayout>
</LinearLayout>

View file

@ -1,65 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--typography, buttons 05.04-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="@dimen/modal_rect_margin_top"
android:contentDescription="@string/content_description_modal_icon"
android:src="@drawable/sheet_top" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginTop="6dp">
<TextView
android:id="@+id/btnEditViewers"
style="@style/TextView.UXStyle.Body"
android:textColor="@color/glyph_active"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:text="@string/edit" />
<TextView
style="@style/TextView.UXStyle.Titles.1"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
android:gravity="center_vertical"
android:text="@string/views" />
<ImageView
android:id="@+id/btnAddNewViewer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|end"
android:contentDescription="@string/content_description_plus_button"
android:paddingTop="@dimen/dp_10"
android:paddingEnd="17dp"
android:paddingStart="@dimen/dp_12"
android:paddingBottom="@dimen/dp_10"
android:src="@drawable/ic_dv_modal_plus"
tools:visibility="visible" />
</FrameLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/dataViewViewerRecycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:paddingBottom="8dp"
tools:listitem="@layout/item_dv_manage_viewer" />
</LinearLayout>

View file

@ -156,4 +156,12 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<androidx.compose.ui.platform.ComposeView
android:id="@+id/viewersWidget"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.motion.widget.MotionLayout>

View file

@ -53,6 +53,8 @@ dependencies {
implementation libs.composeToolingPreview
debugImplementation libs.composeTooling
implementation libs.coilCompose
implementation libs.composeConstraintLayout
implementation libs.composeReorderable
testImplementation libs.fragmentTesting
testImplementation project(':test:android-utils')

View file

@ -1,136 +0,0 @@
package com.anytypeio.anytype.core_ui.features.sets
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.common.AbstractAdapter
import com.anytypeio.anytype.core_ui.common.AbstractViewHolder
import com.anytypeio.anytype.core_ui.databinding.ItemDvManageViewerBinding
import com.anytypeio.anytype.core_ui.databinding.ItemDvManageViewerDoneBinding
import com.anytypeio.anytype.core_ui.tools.SupportDragAndDropBehavior
import com.anytypeio.anytype.core_utils.ext.gone
import com.anytypeio.anytype.core_utils.ext.invisible
import com.anytypeio.anytype.core_utils.ext.shift
import com.anytypeio.anytype.core_utils.ext.toast
import com.anytypeio.anytype.core_utils.ext.visible
import com.anytypeio.anytype.core_utils.ui.ItemTouchHelperViewHolder
import com.anytypeio.anytype.core_utils.ui.OnStartDragListener
import com.anytypeio.anytype.presentation.sets.ManageViewerViewModel.ViewerView
class ManageViewerEditAdapter(
private val onDragListener: OnStartDragListener,
private val onButtonMoreClicked: (ViewerView) -> Unit,
private val onDeleteView: (ViewerView) -> Unit,
private val onDeleteActiveView: () -> Unit
) : AbstractAdapter<ViewerView>(emptyList()), SupportDragAndDropBehavior {
val order: List<String> get() = items.map { it.id }
@SuppressLint("ClickableViewAccessibility")
override fun onCreateViewHolder(
parent: ViewGroup,
iewType: Int
): ViewHolder = ViewHolder(
binding = ItemDvManageViewerBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
).apply {
binding.dndDragger.setOnTouchListener { _, event ->
if (event.action == MotionEvent.ACTION_DOWN) onDragListener.onStartDrag(this)
false
}
binding.icRemove.setOnClickListener {
val pos = bindingAdapterPosition
if (pos != RecyclerView.NO_POSITION) {
onDeleteView(items[pos])
}
}
binding.btnActionMore.setOnClickListener {
val pos = bindingAdapterPosition
if (pos != RecyclerView.NO_POSITION) {
onButtonMoreClicked(items[pos])
}
}
binding.icRemoveInactive.setOnClickListener {
onDeleteActiveView()
}
}
override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
val update = ArrayList(items).shift(fromPosition, toPosition)
items = update
notifyItemMoved(fromPosition, toPosition)
return true
}
class ViewHolder(
val binding: ItemDvManageViewerBinding
) : AbstractViewHolder<ViewerView>(binding.root), ItemTouchHelperViewHolder {
val untitled = binding.root.context.getString(R.string.untitled)
override fun bind(item: ViewerView) {
binding.title.text = item.name.ifEmpty {
untitled
}
if (item.isActive) {
binding.icRemove.gone()
binding.icRemoveInactive.visible()
} else {
binding.icRemove.visible()
binding.icRemoveInactive.gone()
}
}
override fun onItemSelected() {
}
override fun onItemClear() {
}
}
}
class ManageViewerDoneAdapter(
private val onViewerClicked: (ViewerView) -> Unit
) : AbstractAdapter<ViewerView>(emptyList()) {
override fun onCreateViewHolder(
parent: ViewGroup,
iewType: Int
): ViewHolder = ViewHolder(
binding = ItemDvManageViewerDoneBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
).apply {
itemView.setOnClickListener {
onViewerClicked(items[bindingAdapterPosition])
}
}
class ViewHolder(
val binding: ItemDvManageViewerDoneBinding
) : AbstractViewHolder<ViewerView>(binding.root) {
private val title = binding.title
private val icChecked = binding.iconChecked
val untitled = binding.root.context.getString(R.string.untitled)
override fun bind(item: ViewerView) {
title.text = item.name.ifEmpty {
untitled
}
if (item.isActive) {
icChecked.visible()
} else {
icChecked.invisible()
}
}
}
}

View file

@ -7,6 +7,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.Role
import com.anytypeio.anytype.core_ui.extensions.throttledClick
@Composable
fun Modifier.noRippleClickable(
@ -23,4 +24,21 @@ fun Modifier.noRippleClickable(
onClickLabel = onClickLabel,
role = role,
onClick = onClick,
)
@Composable
fun Modifier.noRippleThrottledClickable(
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
indication: Indication? = null,
enabled: Boolean = true,
onClickLabel: String? = null,
role: Role? = null,
onClick: () -> Unit,
) = clickable(
interactionSource = interactionSource,
indication = indication,
enabled = enabled,
onClickLabel = onClickLabel,
role = role,
onClick = throttledClick(onClick),
)

View file

@ -750,7 +750,7 @@ private fun TemplateItemRectangles() {
}
}
private enum class DragStates {
enum class DragStates {
VISIBLE,
DISMISSED
}

View file

@ -0,0 +1,350 @@
package com.anytypeio.anytype.core_ui.widgets.dv
import android.view.HapticFeedbackConstants
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.spring
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.ModalBottomSheetLayout
import androidx.compose.material.ModalBottomSheetValue
import androidx.compose.material.Text
import androidx.compose.material.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension
import androidx.constraintlayout.compose.Visibility
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.foundation.Divider
import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable
import com.anytypeio.anytype.core_ui.views.BodyCalloutRegular
import com.anytypeio.anytype.core_ui.views.Caption2Regular
import com.anytypeio.anytype.core_ui.views.HeadlineSubheading
import com.anytypeio.anytype.core_ui.views.Title1
import com.anytypeio.anytype.presentation.sets.ViewersWidgetUi
import com.anytypeio.anytype.presentation.sets.ViewersWidgetUi.Action.Delete
import com.anytypeio.anytype.presentation.sets.ViewersWidgetUi.Action.Dismiss
import com.anytypeio.anytype.presentation.sets.ViewersWidgetUi.Action.DoneMode
import com.anytypeio.anytype.presentation.sets.ViewersWidgetUi.Action.Edit
import com.anytypeio.anytype.presentation.sets.ViewersWidgetUi.Action.EditMode
import org.burnoutcrew.reorderable.ReorderableItem
import org.burnoutcrew.reorderable.detectReorder
import org.burnoutcrew.reorderable.rememberReorderableLazyListState
import org.burnoutcrew.reorderable.reorderable
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun ViewersWidget(
state: ViewersWidgetUi,
action: (ViewersWidgetUi.Action) -> Unit
) {
val sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
LaunchedEffect(key1 = state, block = {
if (state.showWidget) sheetState.show() else sheetState.hide()
})
DisposableEffect(
key1 = (sheetState.targetValue == ModalBottomSheetValue.Hidden
&& sheetState.isVisible)
) {
onDispose {
if (sheetState.currentValue == ModalBottomSheetValue.Hidden) {
action(Dismiss)
}
}
}
ModalBottomSheetLayout(
sheetState = sheetState,
sheetBackgroundColor = Color.Transparent,
sheetShape = RoundedCornerShape(16.dp),
sheetContent = {
ViewersWidgetContent(state, action)
},
content = {
Modifier
.fillMaxSize()
.background(Color.Black.copy(alpha = 0.4f))
.noRippleThrottledClickable { action.invoke(Dismiss) }
}
)
}
@Composable
private fun ViewersWidgetContent(
state: ViewersWidgetUi,
action: (ViewersWidgetUi.Action) -> Unit
) {
val currentState by rememberUpdatedState(state)
val views = remember { mutableStateOf(currentState.items) }
views.value = currentState.items
val isEditing = remember { mutableStateOf(currentState.isEditing) }
isEditing.value = currentState.isEditing
Box(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.shadow(
elevation = 40.dp,
spotColor = Color(0x40000000),
ambientColor = Color(0x40000000)
)
.padding(start = 8.dp, end = 8.dp, bottom = 31.dp)
.background(
color = colorResource(id = R.color.background_secondary),
shape = RoundedCornerShape(size = 16.dp)
),
) {
Column(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(bottom = 16.dp)
) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(48.dp)
) {
Box(
modifier = Modifier
.align(Alignment.CenterStart),
) {
if (currentState.isEditing) {
ActionText(
text = stringResource(id = R.string.done),
click = { action(DoneMode) }
)
} else {
ActionText(
text = stringResource(id = R.string.edit),
click = { action(EditMode) }
)
}
}
Box(modifier = Modifier.align(Alignment.Center)) {
Text(
text = stringResource(R.string.views),
style = Title1,
color = colorResource(R.color.text_primary)
)
}
Box(modifier = Modifier.align(Alignment.CenterEnd)) {
Image(
modifier = Modifier.padding(
start = 16.dp,
top = 12.dp,
bottom = 12.dp,
end = 16.dp
),
painter = painterResource(id = R.drawable.ic_default_plus),
contentDescription = null
)
}
}
val lazyListState = rememberReorderableLazyListState(
onMove = { from, to ->
views.value = views.value.toMutableList().apply {
add(to.index, removeAt(from.index))
}
},
onDragEnd = { from, to ->
action(
ViewersWidgetUi.Action.OnMove(
currentViews = views.value,
from = from,
to = to
)
)
}
)
LazyColumn(
state = lazyListState.listState,
modifier = Modifier
.reorderable(lazyListState)
.fillMaxWidth()
.wrapContentHeight()
) {
itemsIndexed(
items = views.value,
key = { _, item -> item.id }) { index, view ->
ReorderableItem(
reorderableState = lazyListState,
key = view.id
) { isDragging ->
val currentItem = LocalView.current
if (isDragging) {
currentItem.isHapticFeedbackEnabled = true
currentItem.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
}
val alpha =
animateFloatAsState(if (isDragging) 0.8f else 1.0f, label = "")
ConstraintLayout(
modifier = Modifier
.height(52.dp)
.fillMaxWidth()
.padding(start = 20.dp, end = 20.dp)
.animateContentSize(
animationSpec = spring(
stiffness = Spring.StiffnessLow
)
)
.alpha(alpha.value)
) {
val (delete, text, edit, dnd, unsupported) = createRefs()
Image(
modifier = Modifier
.noRippleThrottledClickable {
action.invoke(Delete(view.id))
}
.constrainAs(delete) {
start.linkTo(parent.start)
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
visibility =
if (isEditing.value && !view.isActive) Visibility.Visible else Visibility.Gone
},
painter = painterResource(id = R.drawable.ic_relation_delete),
contentDescription = "Delete view"
)
Image(
modifier = Modifier
.detectReorder(lazyListState)
.constrainAs(dnd) {
end.linkTo(parent.end)
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
visibility =
if (isEditing.value) Visibility.Visible else Visibility.Gone
},
painter = painterResource(id = R.drawable.ic_dnd),
contentDescription = "Dnd view"
)
Image(
modifier = Modifier
.noRippleThrottledClickable {
action.invoke(Edit(view.id))
}
.constrainAs(edit) {
end.linkTo(dnd.start, margin = 16.dp)
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
visibility =
if (isEditing.value) Visibility.Visible else Visibility.Gone
},
painter = painterResource(id = R.drawable.ic_edit_24),
contentDescription = "Edit view"
)
Text(
modifier = Modifier
.constrainAs(unsupported) {
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
end.linkTo(edit.start)
visibility =
if (!isEditing.value && view.isUnsupported) Visibility.Visible else Visibility.Gone
},
text = stringResource(id = R.string.unsupported),
color = colorResource(id = R.color.text_secondary),
style = Caption2Regular,
textAlign = TextAlign.Left
)
Text(
modifier = Modifier
.noRippleThrottledClickable {
if (!isEditing.value) {
action.invoke(
ViewersWidgetUi.Action.SetActive(
view.id
)
)
}
}
.constrainAs(text) {
start.linkTo(
delete.end,
margin = 12.dp,
goneMargin = 0.dp
)
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
end.linkTo(
unsupported.start,
margin = 8.dp,
goneMargin = 38.dp
)
width = Dimension.fillToConstraints
},
text = view.name,
color = colorResource(id = if (view.isActive) R.color.text_primary else R.color.glyph_active),
style = HeadlineSubheading,
textAlign = TextAlign.Left,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
if (index != views.value.size - 1) {
Divider()
}
}
}
}
}
}
@Composable
private fun ActionText(text: String, click: () -> Unit) {
Text(
modifier = Modifier
.padding(
start = 16.dp,
top = 12.dp,
bottom = 12.dp,
end = 16.dp
)
.noRippleThrottledClickable { click() },
text = text,
style = BodyCalloutRegular,
color = colorResource(id = R.color.glyph_active),
textAlign = TextAlign.Center
)
}

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M19.5,8L21.293,6.207C21.683,5.817 21.683,5.183 21.293,4.793L20.207,3.707C19.817,3.317 19.183,3.317 18.793,3.707L17,5.5L19.5,8ZM6.931,19.759L18,9.4L15.5,7L4.5,17L3,21L6.931,19.759Z"
android:fillColor="@color/glyph_active"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M1.5,6.5C1.224,6.5 1,6.724 1,7C1,7.276 1.224,7.5 1.5,7.5H22.5C22.776,7.5 23,7.276 23,7C23,6.724 22.776,6.5 22.5,6.5H1.5ZM1,12C1,11.724 1.224,11.5 1.5,11.5H22.5C22.776,11.5 23,11.724 23,12C23,12.276 22.776,12.5 22.5,12.5H1.5C1.224,12.5 1,12.276 1,12ZM1,17C1,16.724 1.224,16.5 1.5,16.5H22.5C22.776,16.5 23,16.724 23,17C23,17.276 22.776,17.5 22.5,17.5H1.5C1.224,17.5 1,17.276 1,17Z"
android:fillColor="@color/glyph_active"
android:fillType="evenOdd"/>
</vector>

View file

@ -244,6 +244,7 @@
<string name="relation">Relation</string>
<string name="download">Download</string>
<string name="view">View</string>
<string name="views">Views</string>
<string name="choose_emoji">Choose emoji</string>
<string name="logo_transition">logo_transition</string>
@ -651,5 +652,6 @@
<string name="templates_menu_edit">Edit template</string>
<string name="templates_menu_duplicate">Duplicate</string>
<string name="templates_menu_delete">Delete</string>
<string name="unsupported">Unsupported</string>
</resources>

View file

@ -1,20 +1,22 @@
package com.anytypeio.anytype.domain.dataview.interactor
import com.anytypeio.anytype.domain.base.BaseUseCase
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.core_models.DVViewerType
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.base.ResultInteractor
/**
* Use-case for adding a new viewer to DV.
*/
class AddDataViewViewer(
private val repo: BlockRepository
) : BaseUseCase<Payload, AddDataViewViewer.Params>() {
private val repo: BlockRepository,
dispatchers: AppCoroutineDispatchers
) : ResultInteractor<AddDataViewViewer.Params, Payload>(dispatchers.io) {
override suspend fun run(params: Params) = safe {
repo.addDataViewViewer(
override suspend fun doWork(params: Params): Payload {
return repo.addDataViewViewer(
ctx = params.ctx,
target = params.target,
name = params.name,

View file

@ -2,7 +2,8 @@ package com.anytypeio.anytype.domain.dataview.interactor
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.domain.base.BaseUseCase
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.base.ResultInteractor
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.dataview.interactor.DeleteDataViewViewer.Params
@ -12,11 +13,12 @@ import com.anytypeio.anytype.domain.dataview.interactor.DeleteDataViewViewer.Par
* @see [Params] for details.
*/
class DeleteDataViewViewer(
private val repo: BlockRepository
) : BaseUseCase<Payload, Params>() {
private val repo: BlockRepository,
dispatchers: AppCoroutineDispatchers
) : ResultInteractor<Params, Payload>(dispatchers.io) {
override suspend fun run(params: Params) = safe {
repo.removeDataViewViewer(
override suspend fun doWork(params: Params): Payload {
return repo.removeDataViewViewer(
ctx = params.ctx,
dataview = params.dataview,
viewer = params.viewer

View file

@ -1,22 +1,24 @@
package com.anytypeio.anytype.domain.dataview.interactor
import com.anytypeio.anytype.domain.base.BaseUseCase
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.core_models.DVViewer
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.domain.dataview.interactor.DuplicateDataViewViewer.Params
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.base.ResultInteractor
/**
* Use-case for duplicating data view's view.
* @see [Params] for details.
*/
class DuplicateDataViewViewer(
private val repo: BlockRepository
) : BaseUseCase<Payload, Params>() {
private val repo: BlockRepository,
dispatchers: AppCoroutineDispatchers
) : ResultInteractor<Params, Payload>(dispatchers.io) {
override suspend fun run(params: Params) = safe {
repo.duplicateDataViewViewer(
override suspend fun doWork(params: Params): Payload {
return repo.duplicateDataViewViewer(
context = params.context,
target = params.target,
viewer = params.viewer

View file

@ -1,22 +1,24 @@
package com.anytypeio.anytype.domain.dataview.interactor
import com.anytypeio.anytype.domain.base.BaseUseCase
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.core_models.DVViewer
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.domain.dataview.interactor.RenameDataViewViewer.Params
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.base.ResultInteractor
/**
* Use-case for renaming data view's view.
* @see [Params] for details.
*/
class RenameDataViewViewer(
private val repo: BlockRepository
) : BaseUseCase<Payload, Params>() {
private val repo: BlockRepository,
dispatchers: AppCoroutineDispatchers
) : ResultInteractor<Params, Payload>(dispatchers.io) {
override suspend fun run(params: Params) = safe {
repo.updateDataViewViewer(
override suspend fun doWork(params: Params): Payload {
return repo.updateDataViewViewer(
context = params.context,
target = params.target,
viewer = params.viewer

View file

@ -8,6 +8,7 @@ androidxComposeVersion = '1.4.3'
composeKotlinCompilerVersion = '1.3.1'
composeMaterial3Version = '1.1.1'
composeMaterialVersion = '1.3.1'
composeConstraintLayoutVersion = '1.0.1'
activityComposeVersion = '1.7.2'
composeReorderableVersion = '0.9.6'
@ -80,6 +81,7 @@ composeAccompanistPagerIndicators = { module = "com.google.accompanist:accompani
composeAccompanistThemeAdapter = { module = "com.google.accompanist:accompanist-themeadapter-material", version.ref = "accompanistVersion" }
composeAccompanistNavAnimation = { module = "com.google.accompanist:accompanist-navigation-animation", version.ref = "accompanistVersion" }
composeReorderable = { module = "org.burnoutcrew.composereorderable:reorderable", version.ref = "composeReorderableVersion" }
composeConstraintLayout = { module = "androidx.constraintlayout:constraintlayout-compose", version.ref = "composeConstraintLayoutVersion" }
kotlinxSerializationJson = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version = "1.4.1" }
appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompatVersion" }
androidAnnotations = { module = "androidx.annotation:annotation", version.ref = "appcompatVersion" }

View file

@ -1,110 +0,0 @@
package com.anytypeio.anytype.presentation.sets
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.DVViewerType
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.domain.dataview.interactor.AddDataViewViewer
import com.anytypeio.anytype.presentation.common.BaseViewModel
import com.anytypeio.anytype.presentation.extension.ObjectStateAnalyticsEvent
import com.anytypeio.anytype.presentation.extension.logEvent
import com.anytypeio.anytype.presentation.sets.state.ObjectState
import com.anytypeio.anytype.presentation.util.Dispatcher
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import timber.log.Timber
class CreateDataViewViewerViewModel(
private val addDataViewViewer: AddDataViewViewer,
private val dispatcher: Dispatcher<Payload>,
private val analytics: Analytics,
private val objectState: MutableStateFlow<ObjectState>
) : BaseViewModel() {
val state = MutableStateFlow<ViewState>(ViewState.Init)
private var dvType = DVViewerType.GRID
fun onAddViewer(
name: String,
ctx: String,
target: String,
) {
val startTime = System.currentTimeMillis()
viewModelScope.launch {
addDataViewViewer(
AddDataViewViewer.Params(
ctx = ctx,
target = target,
name = name,
type = dvType
)
).process(
failure = { error ->
Timber.e(error, ERROR_ADD_NEW_VIEW).also {
state.value = ViewState.Error(ERROR_ADD_NEW_VIEW)
}
},
success = {
dispatcher.send(it).also {
logEvent(
state = objectState.value,
analytics = analytics,
event = ObjectStateAnalyticsEvent.ADD_VIEW,
startTime = startTime,
type = dvType.formattedName
)
state.value = ViewState.Completed
}
}
)
}
}
fun onGridClicked() {
dvType = DVViewerType.GRID
state.value = ViewState.Grid
}
fun onListClicked() {
dvType = DVViewerType.LIST
state.value = ViewState.List
}
fun onGalleryClicked() {
dvType = DVViewerType.GALLERY
state.value = ViewState.Gallery
}
class Factory(
private val addDataViewViewer: AddDataViewViewer,
private val dispatcher: Dispatcher<Payload>,
private val analytics: Analytics,
private val objectState: MutableStateFlow<ObjectState>
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return CreateDataViewViewerViewModel(
addDataViewViewer = addDataViewViewer,
dispatcher = dispatcher,
analytics = analytics,
objectState = objectState
) as T
}
}
companion object {
const val ERROR_ADD_NEW_VIEW = "Error while creating a new data view view"
}
sealed class ViewState {
object Init : ViewState()
object Completed : ViewState()
object Grid : ViewState()
object Gallery : ViewState()
object List : ViewState()
data class Error(val msg: String) : ViewState()
}
}

View file

@ -1,286 +0,0 @@
package com.anytypeio.anytype.presentation.sets
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.*
import com.anytypeio.anytype.domain.dataview.interactor.*
import com.anytypeio.anytype.presentation.common.BaseViewModel
import com.anytypeio.anytype.presentation.extension.ObjectStateAnalyticsEvent
import com.anytypeio.anytype.presentation.extension.logEvent
import com.anytypeio.anytype.presentation.sets.state.ObjectState
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
class EditDataViewViewerViewModel(
private val renameDataViewViewer: RenameDataViewViewer,
private val deleteDataViewViewer: DeleteDataViewViewer,
private val duplicateDataViewViewer: DuplicateDataViewViewer,
private val updateDataViewViewer: UpdateDataViewViewer,
private val dispatcher: Dispatcher<Payload>,
private val objectState: StateFlow<ObjectState>,
private val objectSetSession: ObjectSetSession,
private val paginator: ObjectSetPaginator,
private val analytics: Analytics
) : BaseViewModel() {
val viewState = MutableStateFlow<ViewState>(ViewState.Init)
val isDismissed = MutableSharedFlow<Boolean>(replay = 0)
val isLoading = MutableStateFlow(false)
val popupCommands = MutableSharedFlow<PopupMenuCommand>(replay = 0)
var initialName: String = ""
var initialType: DVViewerType = DVViewerType.GRID
private var viewerType: DVViewerType = DVViewerType.GRID
private var viewerName: String = ""
fun onStart(viewerId: Id) {
val state = objectState.value.dataViewState() ?: return
val viewer = state.viewers.firstOrNull { it.id == viewerId }
if (viewer != null) {
initialName = viewer.name
initialType = viewer.type
viewState.value = ViewState.Name(viewer.name)
updateViewState(viewer.type)
} else {
Timber.e("Can't find viewer by id : $viewerId")
}
}
fun onViewerNameChanged(name: String) {
viewerName = name
}
fun onDuplicateClicked(ctx: Id, viewer: Id) {
val startTime = System.currentTimeMillis()
val state = objectState.value.dataViewState() ?: return
viewModelScope.launch {
duplicateDataViewViewer(
DuplicateDataViewViewer.Params(
context = ctx,
target = state.dataViewBlock.id,
viewer = state.viewers.first { it.id == viewer }
)
).process(
failure = { e ->
Timber.e(e, "Error while duplicating viewer: $viewer")
_toasts.emit("Error while deleting viewer: ${e.localizedMessage}")
},
success = {
dispatcher.send(it).also {
logEvent(
state = objectState.value,
analytics = analytics,
event = ObjectStateAnalyticsEvent.DUPLICATE_VIEW,
startTime = startTime,
type = viewerType.formattedName
)
isDismissed.emit(true)
}
}
)
}
}
fun onDeleteClicked(ctx: Id, viewer: Id) {
val state = objectState.value.dataViewState() ?: return
if (state.viewers.size > 1) {
val targetIdx = state.viewers.indexOfFirst { it.id == viewer }
val isActive = if (objectSetSession.currentViewerId.value != null) {
objectSetSession.currentViewerId.value == viewer
} else {
targetIdx == 0
}
var nextViewerId: Id? = null
if (isActive) {
nextViewerId = if (targetIdx != state.viewers.lastIndex)
state.viewers[targetIdx.inc()].id
else
state.viewers[targetIdx.dec()].id
}
proceedWithDeletion(
ctx = ctx,
dv = state.dataViewBlock.id,
viewer = viewer,
nextViewerId = nextViewerId
)
} else {
viewModelScope.launch {
_toasts.emit("Data view should have at least one view")
}
}
}
private fun proceedWithDeletion(
ctx: Id,
dv: Id,
viewer: Id,
nextViewerId: Id?
) {
val startTime = System.currentTimeMillis()
viewModelScope.launch {
deleteDataViewViewer(
DeleteDataViewViewer.Params(
ctx = ctx,
viewer = viewer,
dataview = dv
)
).process(
failure = { e ->
Timber.e(e, "Error while deleting viewer: $viewer")
_toasts.emit("Error while deleting viewer: ${e.localizedMessage}")
},
success = { firstPayload ->
dispatcher.send(firstPayload)
logEvent(
state = objectState.value,
analytics = analytics,
event = ObjectStateAnalyticsEvent.REMOVE_VIEW,
startTime = startTime
)
if (nextViewerId != null) {
objectSetSession.currentViewerId.value = nextViewerId
paginator.offset.value = 0
}
isDismissed.emit(true)
}
)
}
}
fun onMenuClicked(viewer: Id) {
val isDeletionAllowed = isDeletionAllowed(viewer)
viewModelScope.launch {
popupCommands.emit(PopupMenuCommand(isDeletionAllowed = isDeletionAllowed))
}
}
private fun isDeletionAllowed(viewerId: Id): Boolean {
val activeViewerId = objectSetSession.currentViewerId.value
?: (objectState.value.dataViewState()?.viewers?.firstOrNull()?.id ?: false)
return viewerId != activeViewerId
}
fun onDoneClicked(ctx: Id, viewerId: Id) {
Timber.d("onDoneClicked, ctx:[$ctx], viewerId:[$viewerId], viewerType:[${viewerType.formattedName}], viewerName:[$viewerName]")
if (initialName != viewerName || initialType != viewerType) {
updateDVViewerType(ctx, viewerId, viewerType, viewerName)
} else {
viewModelScope.launch { isDismissed.emit(true) }
}
}
fun onGridClicked() {
updateViewState(DVViewerType.GRID)
}
fun onListClicked() {
updateViewState(DVViewerType.LIST)
}
fun onGalleryClicked() {
updateViewState(DVViewerType.GALLERY)
}
private fun updateDVViewerType(ctx: Id, viewerId: Id, type: DVViewerType, name: String) {
val startTime = System.currentTimeMillis()
val state = objectState.value.dataViewState() ?: return
val viewer = state.viewers.find { it.id == viewerId }
if (viewer != null) {
viewModelScope.launch {
isLoading.value = true
updateDataViewViewer(
UpdateDataViewViewer.Params.Fields(
context = ctx,
target = state.dataViewBlock.id,
viewer = viewer.copy(type = type, name = name)
)
).process(
success = { payload ->
dispatcher.send(payload).also {
logEvent(
state = objectState.value,
analytics = analytics,
event = ObjectStateAnalyticsEvent.CHANGE_VIEW_TYPE,
startTime = startTime,
type = type.formattedName
)
isLoading.value = false
isDismissed.emit(true)
}
},
failure = {
isLoading.value = false
Timber.e(it, "Error while updating Viewer type")
isDismissed.emit(true)
}
)
}
} else {
sendToast("View not found. Please, try again later.")
}
}
private fun updateViewState(type: DVViewerType) {
viewerType = type
viewState.value = when (type) {
Block.Content.DataView.Viewer.Type.GRID -> ViewState.Grid
Block.Content.DataView.Viewer.Type.LIST -> ViewState.List
Block.Content.DataView.Viewer.Type.GALLERY -> ViewState.Gallery
Block.Content.DataView.Viewer.Type.BOARD -> ViewState.Kanban
}
}
class Factory(
private val renameDataViewViewer: RenameDataViewViewer,
private val deleteDataViewViewer: DeleteDataViewViewer,
private val duplicateDataViewViewer: DuplicateDataViewViewer,
private val updateDataViewViewer: UpdateDataViewViewer,
private val dispatcher: Dispatcher<Payload>,
private val objectState: StateFlow<ObjectState>,
private val objectSetSession: ObjectSetSession,
private val paginator: ObjectSetPaginator,
private val analytics: Analytics
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return EditDataViewViewerViewModel(
renameDataViewViewer = renameDataViewViewer,
deleteDataViewViewer = deleteDataViewViewer,
duplicateDataViewViewer = duplicateDataViewViewer,
updateDataViewViewer = updateDataViewViewer,
dispatcher = dispatcher,
objectState = objectState,
objectSetSession = objectSetSession,
paginator = paginator,
analytics = analytics
) as T
}
}
private data class ViewerNameUpdate(
val ctx: Id,
val dataview: Id,
val viewer: DVViewer,
val name: String
)
data class PopupMenuCommand(val isDeletionAllowed: Boolean = false)
sealed class ViewState {
object Init : ViewState()
data class Name(val name: String) : ViewState()
object Completed : ViewState()
object Grid : ViewState()
object Gallery : ViewState()
object List : ViewState()
object Kanban : ViewState()
data class Error(val msg: String) : ViewState()
}
}

View file

@ -1,192 +0,0 @@
package com.anytypeio.anytype.presentation.sets
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.DVViewerType
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.domain.base.fold
import com.anytypeio.anytype.domain.dataview.interactor.DeleteDataViewViewer
import com.anytypeio.anytype.domain.dataview.interactor.SetDataViewViewerPosition
import com.anytypeio.anytype.presentation.common.BaseListViewModel
import com.anytypeio.anytype.presentation.extension.ObjectStateAnalyticsEvent
import com.anytypeio.anytype.presentation.extension.logEvent
import com.anytypeio.anytype.presentation.sets.ManageViewerViewModel.ViewerView
import com.anytypeio.anytype.presentation.sets.state.ObjectState
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.flow.filterIsInstance
import kotlinx.coroutines.launch
import timber.log.Timber
class ManageViewerViewModel(
private val objectState: StateFlow<ObjectState>,
private val session: ObjectSetSession,
private val dispatcher: Dispatcher<Payload>,
private val analytics: Analytics,
private val deleteDataViewViewer: DeleteDataViewViewer,
private val setDataViewViewerPosition: SetDataViewViewerPosition
) : BaseListViewModel<ViewerView>() {
val isEditEnabled = MutableStateFlow(false)
val isDismissed = MutableSharedFlow<Boolean>(replay = 0)
val commands = MutableSharedFlow<Command>(replay = 0)
init {
viewModelScope.launch {
objectState.filterIsInstance<ObjectState.DataView>().collect { s ->
_views.value = s.viewers.mapIndexed { index, viewer ->
ViewerView(
id = viewer.id,
name = viewer.name,
type = viewer.type,
isActive = if (session.currentViewerId.value != null)
viewer.id == session.currentViewerId.value
else
index == 0,
showActionMenu = isEditEnabled.value
)
}
}
}
}
fun onOrderChanged(
ctx: Id,
dv: Id,
newPosition: Int,
newOrder: List<Id>,
) {
val currentOrder = views.value.map { it.id }
if (newOrder == currentOrder) return
val viewer = newOrder.getOrNull(newPosition)
if (viewer != null) {
viewModelScope.launch {
// Workaround for preserving the previously first view as the active view
val startTime = System.currentTimeMillis()
if (newPosition == 0 && session.currentViewerId.value.isNullOrEmpty()) {
session.currentViewerId.value = views.value.firstOrNull()?.id
}
setDataViewViewerPosition.stream(
params = SetDataViewViewerPosition.Params(
ctx = ctx,
dv = dv,
viewer = viewer,
pos = newPosition
)
).collect { result ->
logEvent(
state = objectState.value,
analytics = analytics,
event = ObjectStateAnalyticsEvent.REPOSITION_VIEW,
startTime = startTime,
type = views.value.find { it.id == viewer }?.type?.formattedName
)
result.fold(
onSuccess = { dispatcher.send(it) },
onFailure = { Timber.e(it, "Error while changing view order") }
)
}
}
} else {
sendToast("Something went wrong. Please, try again later.")
}
}
fun onViewerActionClicked(view: ViewerView) {
viewModelScope.launch {
commands.emit(Command.OpenEditScreen(view.id, view.name))
}
}
fun onDeleteView(
ctx: Id,
dv: Id,
view: ViewerView
) {
viewModelScope.launch {
deleteDataViewViewer(
DeleteDataViewViewer.Params(
ctx = ctx,
dataview = dv,
viewer = view.id
)
).process(
failure = { Timber.e(it, "Error while deleting view") },
success = { dispatcher.send(it) }
)
}
}
fun onButtonEditClicked() {
isEditEnabled.value = !isEditEnabled.value
_views.value = views.value.map { view ->
view.copy(showActionMenu = isEditEnabled.value)
}
}
fun onButtonAddClicked() {
viewModelScope.launch {
commands.emit(Command.OpenCreateScreen)
}
}
fun onViewerClicked(
ctx: Id,
view: ViewerView
) {
val startTime = System.currentTimeMillis()
if (!isEditEnabled.value)
viewModelScope.launch {
session.currentViewerId.value = view.id
logEvent(
state = objectState.value,
analytics = analytics,
event = ObjectStateAnalyticsEvent.SWITCH_VIEW,
startTime = startTime,
type = view.type.formattedName
)
isDismissed.emit(true)
}
else
Timber.d("Skipping click in edit mode")
}
sealed class Command {
data class OpenEditScreen(val id: Id, val name: String) : Command()
object OpenCreateScreen : Command()
}
class Factory(
private val objectState: StateFlow<ObjectState>,
private val session: ObjectSetSession,
private val dispatcher: Dispatcher<Payload>,
private val analytics: Analytics,
private val deleteDataViewViewer: DeleteDataViewViewer,
private val setDataViewViewerPosition: SetDataViewViewerPosition
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return ManageViewerViewModel(
objectState = objectState,
session = session,
dispatcher = dispatcher,
analytics = analytics,
deleteDataViewViewer = deleteDataViewViewer,
setDataViewViewerPosition = setDataViewViewerPosition
) as T
}
}
data class ViewerView(
val id: Id,
val name: String,
val type: DVViewerType,
val isActive: Boolean,
val showActionMenu: Boolean = false,
)
}

View file

@ -13,18 +13,6 @@ sealed class ObjectSetCommand {
val isFavorite: Boolean
) : Modal()
data class CreateViewer(
val ctx: String,
val target: Id
) : Modal()
data class EditDataViewViewer(
val ctx: Id,
val viewer: Id
) : Modal()
data class ManageViewer(val ctx: Id, val dataview: Id) : Modal()
data class OpenSettings(
val ctx: Id,
val dv: Id,

View file

@ -1,6 +1,5 @@
package com.anytypeio.anytype.presentation.sets
import android.util.Log
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.CoverType
import com.anytypeio.anytype.core_models.DVFilter
@ -8,6 +7,7 @@ import com.anytypeio.anytype.core_models.DVRecord
import com.anytypeio.anytype.core_models.DVSort
import com.anytypeio.anytype.core_models.DVViewer
import com.anytypeio.anytype.core_models.DVViewerRelation
import com.anytypeio.anytype.core_models.DVViewerType
import com.anytypeio.anytype.core_models.Event.Command.DataView.UpdateView.DVFilterUpdate
import com.anytypeio.anytype.core_models.Event.Command.DataView.UpdateView.DVSortUpdate
import com.anytypeio.anytype.core_models.Event.Command.DataView.UpdateView.DVViewerFields
@ -44,6 +44,7 @@ import com.anytypeio.anytype.presentation.sets.model.ObjectView
import com.anytypeio.anytype.presentation.sets.model.SimpleRelationView
import com.anytypeio.anytype.presentation.sets.model.Viewer
import com.anytypeio.anytype.presentation.sets.state.ObjectState
import com.anytypeio.anytype.presentation.sets.viewer.ViewerView
import com.anytypeio.anytype.presentation.templates.TemplateView
fun ObjectState.DataView.featuredRelations(
@ -479,4 +480,19 @@ fun ObjectWrapper.Type.toTemplateViewBlank(): TemplateView.Blank {
typeId = id,
layout = recommendedLayout?.code ?: ObjectType.Layout.BASIC.code
)
}
fun List<DVViewer>.toView(session: ObjectSetSession): List<ViewerView> {
return mapIndexed { index, viewer ->
ViewerView(
id = viewer.id,
name = viewer.name,
type = viewer.type,
isActive = if (session.currentViewerId.value != null)
viewer.id == session.currentViewerId.value
else
index == 0,
isUnsupported = viewer.type == DVViewerType.BOARD
)
}
}

View file

@ -68,6 +68,9 @@ import com.anytypeio.anytype.presentation.sets.model.Viewer
import com.anytypeio.anytype.presentation.sets.state.ObjectState
import com.anytypeio.anytype.presentation.sets.state.ObjectStateReducer
import com.anytypeio.anytype.presentation.sets.subscription.DataViewSubscription
import com.anytypeio.anytype.presentation.sets.viewer.ViewerDelegate
import com.anytypeio.anytype.presentation.sets.viewer.ViewerEvent
import com.anytypeio.anytype.presentation.sets.viewer.ViewerView
import com.anytypeio.anytype.presentation.templates.ObjectTypeTemplatesContainer
import com.anytypeio.anytype.presentation.templates.TemplateMenuClick
import com.anytypeio.anytype.presentation.templates.TemplateView
@ -131,8 +134,9 @@ class ObjectSetViewModel(
private val updateDataViewViewer: UpdateDataViewViewer,
private val duplicateObjects: DuplicateObjects,
private val templatesContainer: ObjectTypeTemplatesContainer,
private val setObjectListIsArchived: SetObjectListIsArchived
) : ViewModel(), SupportNavigation<EventWrapper<AppNavigation.Command>> {
private val setObjectListIsArchived: SetObjectListIsArchived,
private val viewerDelegate: ViewerDelegate
) : ViewModel(), SupportNavigation<EventWrapper<AppNavigation.Command>>, ViewerDelegate by viewerDelegate {
val status = MutableStateFlow(SyncStatus.UNKNOWN)
val error = MutableStateFlow<String?>(null)
@ -160,6 +164,7 @@ class ObjectSetViewModel(
val currentViewer = _currentViewer
private val _templateViews = MutableStateFlow<List<TemplateView>>(emptyList())
private val _dvViews = MutableStateFlow<List<ViewerView>>(emptyList())
private val _header = MutableStateFlow<SetOrCollectionHeaderState>(
SetOrCollectionHeaderState.None
@ -168,6 +173,7 @@ class ObjectSetViewModel(
val isCustomizeViewPanelVisible = MutableStateFlow(false)
val templatesWidgetState = MutableStateFlow(TemplatesWidgetUiState.init())
val viewersWidgetState = MutableStateFlow(ViewersWidgetUi.init())
@Deprecated("could be deleted")
val isLoading = MutableStateFlow(false)
@ -266,6 +272,12 @@ class ObjectSetViewModel(
templatesWidgetState.value = templatesWidgetState.value.copy(items = it)
}
}
viewModelScope.launch {
_dvViews.collectLatest {
viewersWidgetState.value = viewersWidgetState.value.copy(items = it)
}
}
}
private suspend fun proceedWithSettingUnsplashImage(
@ -458,7 +470,6 @@ class ObjectSetViewModel(
dataViewState = dataViewState,
objectState = objectState,
currentViewId = currentViewId,
templates = templates
)
ObjectState.Init -> DataViewViewState.Init
ObjectState.ErrorLayout -> DataViewViewState.Error(msg = "Wrong layout, couldn't open object")
@ -477,6 +488,7 @@ class ObjectSetViewModel(
return when (dataViewState) {
DataViewState.Init -> {
_dvViews.value = emptyList()
if (dvViewer == null) {
DataViewViewState.Collection.NoView
} else {
@ -484,6 +496,7 @@ class ObjectSetViewModel(
}
}
is DataViewState.Loaded -> {
_dvViews.value = objectState.viewers.toView(session)
val relations = objectState.dataViewContent.relationLinks.mapNotNull {
storeOfRelations.getByKey(it.key)
}
@ -520,7 +533,6 @@ class ObjectSetViewModel(
dataViewState: DataViewState,
objectState: ObjectState.DataView.Set,
currentViewId: String?,
templates: List<TemplateView>
): DataViewViewState {
if (!objectState.isInitialized) return DataViewViewState.Init
@ -530,6 +542,7 @@ class ObjectSetViewModel(
return when (dataViewState) {
DataViewState.Init -> {
_dvViews.value = emptyList()
when {
setOfValue.isEmpty() || query.isEmpty() -> DataViewViewState.Set.NoQuery
viewer == null -> DataViewViewState.Set.NoView
@ -537,6 +550,7 @@ class ObjectSetViewModel(
}
}
is DataViewState.Loaded -> {
_dvViews.value = objectState.viewers.toView(session)
val relations = objectState.dataViewContent.relationLinks.mapNotNull {
storeOfRelations.getByKey(it.key)
}
@ -1043,16 +1057,12 @@ class ObjectSetViewModel(
fun onExpandViewerMenuClicked() {
Timber.d("onExpandViewerMenuClicked, ")
val state = stateReducer.state.value.dataViewState() ?: return
if (isRestrictionPresent(DataViewRestriction.VIEWS)
) {
toast(NOT_ALLOWED)
} else {
dispatch(
ObjectSetCommand.Modal.ManageViewer(
ctx = context,
dataview = state.dataViewBlock.id
)
viewersWidgetState.value = viewersWidgetState.value.copy(
showWidget = true
)
}
}
@ -1061,12 +1071,6 @@ class ObjectSetViewModel(
Timber.d("onViewerEditClicked, ")
val state = stateReducer.state.value.dataViewState() ?: return
val viewer = state.viewerById(session.currentViewerId.value) ?: return
dispatch(
ObjectSetCommand.Modal.EditDataViewViewer(
ctx = context,
viewer = viewer.id
)
)
}
fun onMenuClicked() {
@ -1748,6 +1752,66 @@ class ObjectSetViewModel(
}
//endregion
// region VIEWS
fun onViewersWidgetAction(action: ViewersWidgetUi.Action) {
when (action) {
ViewersWidgetUi.Action.Dismiss -> {
viewersWidgetState.value = viewersWidgetState.value.copy(
showWidget = false,
isEditing = false
)
}
ViewersWidgetUi.Action.DoneMode -> {
viewersWidgetState.value = viewersWidgetState.value.copy(isEditing = false)
}
ViewersWidgetUi.Action.EditMode -> {
viewersWidgetState.value = viewersWidgetState.value.copy(isEditing = true)
}
is ViewersWidgetUi.Action.Delete -> {
val state = stateReducer.state.value.dataViewState() ?: return
viewModelScope.launch {
onEvent(
ViewerEvent.Delete(
ctx = context,
dv = state.dataViewBlock.id,
viewer = action.viewer,
)
)
}
}
is ViewersWidgetUi.Action.Edit -> TODO()
is ViewersWidgetUi.Action.OnMove -> {
Timber.d("onMove Viewer, from:[$action.from], to:[$action.to]")
if (action.from == action.to) return
val state = stateReducer.state.value.dataViewState() ?: return
if (action.to == 0 && session.currentViewerId.value.isNullOrEmpty()) {
session.currentViewerId.value = action.currentViews.firstOrNull()?.id
}
viewModelScope.launch {
viewerDelegate.onEvent(
ViewerEvent.UpdatePosition(
ctx = context,
dv = state.dataViewBlock.id,
viewer = action.currentViews[action.to].id,
position = action.to
)
)
}
}
is ViewersWidgetUi.Action.SetActive -> {
viewModelScope.launch {
onEvent(ViewerEvent.SetActive(viewer = action.id))
}
}
ViewersWidgetUi.Action.Plus -> {
}
}
}
//endregion
companion object {
const val NOT_ALLOWED = "Not allowed for this set"
const val NOT_ALLOWED_CELL = "Not allowed for this cell"

View file

@ -33,6 +33,7 @@ import com.anytypeio.anytype.presentation.common.Delegator
import com.anytypeio.anytype.presentation.editor.cover.CoverImageHashProvider
import com.anytypeio.anytype.presentation.sets.state.ObjectStateReducer
import com.anytypeio.anytype.presentation.sets.subscription.DataViewSubscription
import com.anytypeio.anytype.presentation.sets.viewer.ViewerDelegate
import com.anytypeio.anytype.presentation.templates.ObjectTypeTemplatesContainer
import com.anytypeio.anytype.presentation.util.Dispatcher
@ -70,7 +71,8 @@ class ObjectSetViewModelFactory(
private val updateDataViewViewer: UpdateDataViewViewer,
private val duplicateObjects: DuplicateObjects,
private val templatesContainer: ObjectTypeTemplatesContainer,
private val setObjectListIsArchived: SetObjectListIsArchived
private val setObjectListIsArchived: SetObjectListIsArchived,
private val viewerDelegate: ViewerDelegate
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@ -108,7 +110,8 @@ class ObjectSetViewModelFactory(
updateDataViewViewer = updateDataViewViewer,
duplicateObjects = duplicateObjects,
templatesContainer = templatesContainer,
setObjectListIsArchived = setObjectListIsArchived
setObjectListIsArchived = setObjectListIsArchived,
viewerDelegate = viewerDelegate
) as T
}
}

View file

@ -0,0 +1,45 @@
package com.anytypeio.anytype.presentation.sets
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.presentation.sets.viewer.ViewerView
import com.anytypeio.anytype.presentation.widgets.FromIndex
import com.anytypeio.anytype.presentation.widgets.ToIndex
data class ViewersWidgetUi(
val showWidget: Boolean,
val isEditing: Boolean,
val items: List<ViewerView>
) {
fun dismiss() = copy(
showWidget = false,
isEditing = false
)
companion object {
fun init() = ViewersWidgetUi(
showWidget = false,
isEditing = false,
items = emptyList()
)
}
sealed class Action {
object Dismiss : Action()
object EditMode : Action()
object DoneMode : Action()
data class Delete(val viewer: Id) : Action()
data class Edit(val id: Id) : Action()
data class OnMove(
val currentViews: List<ViewerView>,
val from: FromIndex,
val to: ToIndex
) : Action()
data class SetActive(val id: Id) : Action()
object Plus : Action()
}
}

View file

@ -0,0 +1,48 @@
package com.anytypeio.anytype.presentation.sets.viewer
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.core_models.DVViewer
import com.anytypeio.anytype.core_models.DVViewerType
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.domain.base.fold
import com.anytypeio.anytype.domain.dataview.interactor.AddDataViewViewer
import com.anytypeio.anytype.domain.dataview.interactor.DeleteDataViewViewer
import com.anytypeio.anytype.domain.dataview.interactor.DuplicateDataViewViewer
import com.anytypeio.anytype.domain.dataview.interactor.RenameDataViewViewer
import com.anytypeio.anytype.domain.dataview.interactor.SetDataViewViewerPosition
import com.anytypeio.anytype.presentation.sets.ObjectSetSession
import com.anytypeio.anytype.presentation.util.Dispatcher
import javax.inject.Inject
import timber.log.Timber
interface ViewerDelegate {
suspend fun onEvent(event: ViewerEvent)
}
sealed class ViewerEvent {
data class Delete(val ctx: Id, val dv: Id, val viewer: Id) : ViewerEvent()
data class Duplicate(val ctx: Id, val dv: Id, val viewer: DVViewer) : ViewerEvent()
data class Rename(val ctx: Id, val dv: Id, val viewer: DVViewer) : ViewerEvent()
data class AddNew(val ctx: Id, val dv: Id, val name: String, val type: DVViewerType) :
ViewerEvent()
data class UpdatePosition(val ctx: Id, val dv: Id, val viewer: Id, val position: Int) :
ViewerEvent()
data class SetActive(val viewer: Id) : ViewerEvent()
}
class DefaultViewerDelegate @Inject constructor(
private val session: ObjectSetSession,
private val dispatcher: Dispatcher<Payload>,
private val analytics: Analytics,
private val deleteDataViewViewer: DeleteDataViewViewer,
private val setDataViewViewerPosition: SetDataViewViewerPosition,
private val duplicateDataViewViewer: DuplicateDataViewViewer,
private val addDataViewViewer: AddDataViewViewer,
private val renameDataViewViewer: RenameDataViewViewer
) : ViewerDelegate {
override suspend fun onEvent(event: ViewerEvent) {}
}

View file

@ -0,0 +1,15 @@
package com.anytypeio.anytype.presentation.sets.viewer
import com.anytypeio.anytype.core_models.DVViewerType
import com.anytypeio.anytype.core_models.Id
data class ViewerView(
val id: Id,
val name: String,
val type: DVViewerType,
val isActive: Boolean,
val showActionMenu: Boolean = false,
val isUnsupported: Boolean = false
)

View file

@ -60,6 +60,7 @@ import com.anytypeio.anytype.presentation.sets.state.DefaultObjectStateReducer
import com.anytypeio.anytype.presentation.sets.subscription.DataViewSubscription
import com.anytypeio.anytype.presentation.sets.subscription.DefaultDataViewSubscription
import com.anytypeio.anytype.presentation.sets.updateFormatForSubscription
import com.anytypeio.anytype.presentation.sets.viewer.ViewerDelegate
import com.anytypeio.anytype.presentation.templates.ObjectTypeTemplatesContainer
import com.anytypeio.anytype.presentation.util.DefaultCoroutineTestRule
import com.anytypeio.anytype.presentation.util.Dispatcher
@ -166,6 +167,9 @@ open class ObjectSetViewModelTestSetup {
@Mock
lateinit var updateDataViewViewer: UpdateDataViewViewer
@Mock
lateinit var viewerDelegate: ViewerDelegate
var stateReducer = DefaultObjectStateReducer()
lateinit var dataViewSubscriptionContainer: DataViewSubscriptionContainer
@ -237,7 +241,8 @@ open class ObjectSetViewModelTestSetup {
updateDataViewViewer = updateDataViewViewer,
templatesContainer = templatesContainer,
setObjectListIsArchived = setObjectListIsArchived,
duplicateObjects = duplicateObjects
duplicateObjects = duplicateObjects,
viewerDelegate = viewerDelegate
)
}

View file

@ -1,330 +0,0 @@
package com.anytypeio.anytype.presentation.sets.main
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.core_models.*
import com.anytypeio.anytype.domain.base.Either
import com.anytypeio.anytype.domain.dataview.interactor.*
import com.anytypeio.anytype.presentation.sets.EditDataViewViewerViewModel
import com.anytypeio.anytype.presentation.sets.ObjectSetPaginator
import com.anytypeio.anytype.presentation.sets.ObjectSetSession
import com.anytypeio.anytype.presentation.sets.state.ObjectState
import com.anytypeio.anytype.presentation.util.CoroutinesTestRule
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.test_utils.MockDataFactory
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.Mock
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.*
class ObjectSetViewerDeleteTest {
@get:Rule
val coroutineTestRule = CoroutinesTestRule()
val title = Block(
id = MockDataFactory.randomUuid(),
content = Block.Content.Text(
style = Block.Content.Text.Style.TITLE,
text = MockDataFactory.randomString(),
marks = emptyList()
),
children = emptyList(),
fields = Block.Fields.empty()
)
val header = Block(
id = MockDataFactory.randomUuid(),
content = Block.Content.Layout(
type = Block.Content.Layout.Type.HEADER
),
fields = Block.Fields.empty(),
children = listOf(title.id)
)
val paginator = ObjectSetPaginator()
@Mock
lateinit var dispatcher: Dispatcher<Payload>
@Mock
lateinit var renameDataViewViewer: RenameDataViewViewer
@Mock
lateinit var deleteDataViewViewer: DeleteDataViewViewer
@Mock
lateinit var duplicateDataViewViewer: DuplicateDataViewViewer
@Mock
lateinit var updateDataViewViewer: UpdateDataViewViewer
@Mock
lateinit var analytics: Analytics
private val ctx: Id = MockDataFactory.randomUuid()
@Before
fun setup() {
MockitoAnnotations.openMocks(this)
}
@Test
fun `should not update active view after inactive view is deleted`() = runTest {
// SETUP
val firstViewer = DVViewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
viewerRelations = emptyList(),
type = DVViewerType.GRID,
sorts = emptyList(),
filters = emptyList()
)
val secondViewer = firstViewer.copy(id = MockDataFactory.randomUuid())
val thirdViewer = firstViewer.copy(id = MockDataFactory.randomUuid())
val activeViewerId = firstViewer.id
val objectSetSession = ObjectSetSession().apply {
currentViewerId.value = activeViewerId
}
val dv = Block(
id = MockDataFactory.randomUuid(),
content = DV(
viewers = listOf(firstViewer, secondViewer, thirdViewer)
),
children = emptyList(),
fields = Block.Fields.empty()
)
val objectSetState = MutableStateFlow(
ObjectState.DataView.Set(
root = ctx,
blocks = listOf(
header,
title,
dv
)
)
)
stubRemoveDataViewViewer(
dv = dv.id,
viewer = secondViewer.id
)
val vm = buildViewModel(
objectSetState = objectSetState,
objectSetSession = objectSetSession,
updateDataViewViewer = updateDataViewViewer
)
// TESTING
vm.onDeleteClicked(
ctx = ctx,
viewer = secondViewer.id
)
verifyBlocking(deleteDataViewViewer, times(1)) {
invoke(
DeleteDataViewViewer.Params(
ctx = ctx,
viewer = secondViewer.id,
dataview = dv.id
)
)
}
}
@Test
fun `should set next view as active view if currently active view is being deleted`() {
// SETUP
val firstViewer = DVViewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
viewerRelations = emptyList(),
type = DVViewerType.GRID,
sorts = emptyList(),
filters = emptyList()
)
val secondViewer = firstViewer.copy(id = MockDataFactory.randomUuid())
val thirdViewer = firstViewer.copy(id = MockDataFactory.randomUuid())
val activeViewerId = firstViewer.id
val objectSetSession = ObjectSetSession().apply {
currentViewerId.value = activeViewerId
}
val dv = Block(
id = MockDataFactory.randomUuid(),
content = DV(
viewers = listOf(firstViewer, secondViewer, thirdViewer)
),
children = emptyList(),
fields = Block.Fields.empty()
)
val objectSetState = MutableStateFlow(
ObjectState.DataView.Set(
root = ctx,
blocks = listOf(
header,
title,
dv
)
)
)
stubRemoveDataViewViewer(
dv = dv.id,
viewer = firstViewer.id
)
val vm = buildViewModel(
objectSetState = objectSetState,
objectSetSession = objectSetSession,
updateDataViewViewer = updateDataViewViewer
)
// TESTING
vm.onDeleteClicked(
ctx = ctx,
viewer = firstViewer.id
)
verifyBlocking(deleteDataViewViewer, times(1)) {
invoke(
DeleteDataViewViewer.Params(
ctx = ctx,
viewer = firstViewer.id,
dataview = dv.id
)
)
}
}
@Test
fun `should set prevous view as active view if currently active view is being deleted`() {
// SETUP
val firstViewer = DVViewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
viewerRelations = emptyList(),
type = DVViewerType.GRID,
sorts = emptyList(),
filters = emptyList()
)
val secondViewer = firstViewer.copy(id = MockDataFactory.randomUuid())
val thirdViewer = firstViewer.copy(id = MockDataFactory.randomUuid())
val activeViewerId = thirdViewer.id
val objectSetSession = ObjectSetSession().apply {
currentViewerId.value = activeViewerId
}
val dv = Block(
id = MockDataFactory.randomUuid(),
content = DV(
viewers = listOf(firstViewer, secondViewer, thirdViewer)
),
children = emptyList(),
fields = Block.Fields.empty()
)
val objectSetState = MutableStateFlow(
ObjectState.DataView.Set(
root = ctx,
blocks = listOf(
header,
title,
dv
)
)
)
stubRemoveDataViewViewer(
dv = dv.id,
viewer = thirdViewer.id
)
val vm = buildViewModel(
objectSetState = objectSetState,
objectSetSession = objectSetSession,
updateDataViewViewer = updateDataViewViewer
)
// TESTING
vm.onDeleteClicked(
ctx = ctx,
viewer = thirdViewer.id
)
verifyBlocking(deleteDataViewViewer, times(1)) {
invoke(
DeleteDataViewViewer.Params(
ctx = ctx,
viewer = thirdViewer.id,
dataview = dv.id
)
)
}
}
fun buildViewModel(
updateDataViewViewer: UpdateDataViewViewer,
objectSetState: StateFlow<ObjectState>,
objectSetSession: ObjectSetSession
): EditDataViewViewerViewModel {
return EditDataViewViewerViewModel(
renameDataViewViewer = renameDataViewViewer,
deleteDataViewViewer = deleteDataViewViewer,
duplicateDataViewViewer = duplicateDataViewViewer,
objectSetSession = objectSetSession,
objectState = objectSetState,
dispatcher = dispatcher,
updateDataViewViewer = updateDataViewViewer,
analytics = analytics,
paginator = paginator
)
}
private fun stubRemoveDataViewViewer(
viewer: Id,
dv: Id
) {
deleteDataViewViewer.stub {
onBlocking {
invoke(
DeleteDataViewViewer.Params(
ctx = ctx,
viewer = viewer,
dataview = dv
)
)
} doReturn Either.Right(
Payload(
context = ctx,
events = emptyList()
)
)
}
}
}