diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/DashboardDi.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/DashboardDi.kt index 204f357d5b..a257c291c7 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/DashboardDi.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/DashboardDi.kt @@ -16,6 +16,8 @@ import com.anytypeio.anytype.domain.event.interactor.EventChannel import com.anytypeio.anytype.domain.event.interactor.InterceptEvents import com.anytypeio.anytype.domain.icon.DocumentEmojiIconProvider import com.anytypeio.anytype.domain.misc.UrlBuilder +import com.anytypeio.anytype.domain.objects.DeleteObjects +import com.anytypeio.anytype.domain.objects.SetObjectListIsArchived import com.anytypeio.anytype.domain.page.CreatePage import com.anytypeio.anytype.presentation.dashboard.HomeDashboardEventConverter import com.anytypeio.anytype.presentation.dashboard.HomeDashboardViewModelFactory @@ -59,7 +61,9 @@ object HomeDashboardModule { getDebugSettings: GetDebugSettings, analytics: Analytics, searchObjects: SearchObjects, - urlBuilder: UrlBuilder + urlBuilder: UrlBuilder, + setObjectListIsArchived: SetObjectListIsArchived, + deleteObjects: DeleteObjects ): HomeDashboardViewModelFactory = HomeDashboardViewModelFactory( getProfile = getProfile, openDashboard = openDashboard, @@ -72,7 +76,9 @@ object HomeDashboardModule { getDebugSettings = getDebugSettings, searchObjects = searchObjects, analytics = analytics, - urlBuilder = urlBuilder + urlBuilder = urlBuilder, + setObjectListIsArchived = setObjectListIsArchived, + deleteObjects = deleteObjects ) @JvmStatic @@ -171,4 +177,22 @@ object HomeDashboardModule { ) : SearchObjects = SearchObjects( repo = repo ) + + @JvmStatic + @Provides + @PerScreen + fun deleteObjects( + repo: BlockRepository + ) : DeleteObjects = DeleteObjects( + repo = repo + ) + + @JvmStatic + @Provides + @PerScreen + fun setObjectListIsArchived( + repo: BlockRepository + ) : SetObjectListIsArchived = SetObjectListIsArchived( + repo = repo + ) } \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DashboardAdapter.kt b/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DashboardAdapter.kt index 3d9d7a7877..7b17a049ab 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DashboardAdapter.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DashboardAdapter.kt @@ -195,6 +195,8 @@ class DashboardAdapter( } } } + + holder.bindSelection(data[position].isSelected) } override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: MutableList) { @@ -250,6 +252,9 @@ class DashboardAdapter( if (payload.isLoadingChanged) { bindLoading(item.isLoading) } + if (payload.isSelectionChanged) { + bindSelection(item.isSelected) + } } } @@ -266,18 +271,29 @@ class DashboardAdapter( if (payload.isLoadingChanged) { bindLoading(item.isLoading) } + if (payload.isSelectionChanged) { + bindSelection(item.isSelected) + } } } sealed class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + abstract fun bindSelection(isSelected: Boolean) + class ArchiveHolder(itemView: View) : ViewHolder(itemView) { + private val selection = itemView.findViewById(R.id.ivSelection) + fun bindTitle(title: String) { if (title.isNotEmpty()) { itemView.archiveTitle.text = title } } + + override fun bindSelection(isSelected: Boolean) { + if (isSelected) selection.visible() else selection.invisible() + } } class DocumentHolder(itemView: View) : ViewHolder(itemView) { @@ -285,6 +301,7 @@ class DashboardAdapter( private val tvTitle = itemView.title private val tvSubtitle = itemView.typeTitle private val shimmer = itemView.shimmer + private val selection = itemView.findViewById(R.id.ivSelection) fun bindTitle(title: String?) { if (title.isNullOrEmpty()) @@ -312,6 +329,10 @@ class DashboardAdapter( tvTitle.visible() } } + + override fun bindSelection(isSelected: Boolean) { + if (isSelected) selection.visible() else selection.invisible() + } } class DocumentWithoutIconViewHolder(parent: ViewGroup) : ViewHolder( @@ -325,6 +346,7 @@ class DashboardAdapter( private val tvTitle = itemView.findViewById(R.id.tvDocTitle) private val tvSubtitle = itemView.findViewById(R.id.tvDocTypeName) private val shimmer = itemView.findViewById(R.id.shimmer) + private val selection = itemView.findViewById(R.id.ivSelection) fun bindTitle(title: String?) { tvTitle.text = title @@ -347,6 +369,10 @@ class DashboardAdapter( tvSubtitle.visible() } } + + override fun bindSelection(isSelected: Boolean) { + if (isSelected) selection.visible() else selection.invisible() + } } class DocumentTaskViewHolder(parent: ViewGroup) : ViewHolder( @@ -361,6 +387,7 @@ class DashboardAdapter( private val tvSubtitle = itemView.findViewById(R.id.tvDocTypeName) private val checkbox = itemView.findViewById(R.id.ivCheckbox) private val shimmer = itemView.findViewById(R.id.shimmer) + private val selection = itemView.findViewById(R.id.ivSelection) fun bindTitle(title: String?) { tvTitle.text = title @@ -391,12 +418,17 @@ class DashboardAdapter( checkbox.setImageResource(R.drawable.ic_dashboard_task_checkbox_not_checked) } } + + override fun bindSelection(isSelected: Boolean) { + if (isSelected) selection.visible() else selection.invisible() + } } class ObjectSetHolder(itemView: View) : ViewHolder(itemView) { private val tvTitle = itemView.title private val shimmer = itemView.shimmer + private val selection = itemView.findViewById(R.id.ivSelection) fun bindLoading(isLoading: Boolean) { if (isLoading) { @@ -420,12 +452,17 @@ class DashboardAdapter( fun bindIcon(icon: ObjectIcon) { itemView.iconWidget.bind(icon) } + + override fun bindSelection(isSelected: Boolean) { + if (isSelected) selection.visible() else selection.invisible() + } } class ObjectSetWithoutIconHolder(itemView: View) : ViewHolder(itemView) { private val tvTitle = itemView.findViewById(R.id.tvSetTitle) private val shimmer = itemView.findViewById(R.id.shimmer) + private val selection = itemView.findViewById(R.id.ivSelection) fun bindLoading(isLoading: Boolean) { if (isLoading) { @@ -445,6 +482,10 @@ class DashboardAdapter( else tvTitle.text = title } + + override fun bindSelection(isSelected: Boolean) { + if (isSelected) selection.visible() else selection.invisible() + } } } diff --git a/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DashboardFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DashboardFragment.kt index cef1a5c1ff..ee09730798 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DashboardFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DashboardFragment.kt @@ -2,13 +2,17 @@ package com.anytypeio.anytype.ui.dashboard import android.os.Bundle import android.view.View +import android.view.animation.AccelerateDecelerateInterpolator +import android.view.animation.LinearInterpolator +import androidx.constraintlayout.widget.ConstraintSet import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope +import androidx.transition.ChangeBounds +import androidx.transition.TransitionManager +import androidx.transition.TransitionSet import com.anytypeio.anytype.R import com.anytypeio.anytype.core_ui.reactive.clicks -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.ext.* import com.anytypeio.anytype.di.common.componentManager import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.presentation.dashboard.DashboardView @@ -133,6 +137,51 @@ class DashboardFragment : ViewStateFragment(R.layout.fragment_dashboard) lifecycleScope.subscribe(vm.recent) { dashboardRecentAdapter.update(it) } lifecycleScope.subscribe(vm.sets) { dashboardSetsAdapter.update(it) } lifecycleScope.subscribe(vm.archived) { dashboardArchiveAdapter.update(it) } + lifecycleScope.subscribe(vm.count) { tvSelectedCount.text = "$it object selected" } + lifecycleScope.subscribe(vm.mode) { mode -> + when(mode) { + HomeDashboardViewModel.Mode.DEFAULT -> { + selectionTopToolbar.invisible() + tabsLayout.visible() + val set = ConstraintSet().apply { + clone(dashboardRoot) + clear(R.id.selectionBottomToolbar, ConstraintSet.BOTTOM) + connect( + R.id.selectionBottomToolbar, + ConstraintSet.TOP, + R.id.dashboardRoot, + ConstraintSet.BOTTOM + ) + } + val transitionSet = TransitionSet().apply { + addTransition(ChangeBounds()) + duration = 100 + } + TransitionManager.beginDelayedTransition(dashboardRoot, transitionSet) + set.applyTo(dashboardRoot) + } + HomeDashboardViewModel.Mode.SELECTION -> { + tabsLayout.invisible() + selectionTopToolbar.visible() + val set = ConstraintSet().apply { + clone(dashboardRoot) + clear(R.id.selectionBottomToolbar, ConstraintSet.TOP) + connect( + R.id.selectionBottomToolbar, + ConstraintSet.BOTTOM, + R.id.dashboardRoot, + ConstraintSet.BOTTOM + ) + } + val transitionSet = TransitionSet().apply { + addTransition(ChangeBounds()) + duration = 100 + } + TransitionManager.beginDelayedTransition(dashboardRoot, transitionSet) + set.applyTo(dashboardRoot) + } + } + } } override fun onPause() { @@ -228,6 +277,26 @@ class DashboardFragment : ViewStateFragment(R.layout.fragment_dashboard) .clicks() .onEach { vm.onAvatarClicked() } .launchIn(lifecycleScope) + + tvCancel + .clicks() + .onEach { vm.onCancelSelectionClicked() } + .launchIn(lifecycleScope) + + tvSelectAll + .clicks() + .onEach { vm.onSelectAllClicked() } + .launchIn(lifecycleScope) + + tvPutBack + .clicks() + .onEach { vm.onPutBackClicked() } + .launchIn(lifecycleScope) + + tvDelete + .clicks() + .onEach { vm.onDeleteObjectsClicked() } + .launchIn(lifecycleScope) } override fun injectDependencies() { diff --git a/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DesktopDiffUtil.kt b/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DesktopDiffUtil.kt index 6e3220534a..0f2bb91372 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DesktopDiffUtil.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DesktopDiffUtil.kt @@ -64,6 +64,10 @@ class DesktopDiffUtil( } } + if (oldDoc.isSelected != newDoc.isSelected) { + changes.add(SELECTION_CHANGED) + } + return if (changes.isNotEmpty()) { Payload(changes).also { Timber.d("Returning payload: $it") } } else { @@ -76,6 +80,7 @@ class DesktopDiffUtil( ) { val isLoadingChanged: Boolean = changes.contains(LOADING_STATE_CHANGED) + val isSelectionChanged: Boolean = changes.contains(SELECTION_CHANGED) fun targetChanged() = changes.contains(TARGET_CHANGED) fun titleChanged() = changes.contains(TITLE_CHANGED) @@ -89,5 +94,6 @@ class DesktopDiffUtil( const val EMOJI_CHANGED = 4 const val IMAGE_CHANGED = 5 const val LOADING_STATE_CHANGED = 6 + const val SELECTION_CHANGED = 7 } } \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_dashboard.xml b/app/src/main/res/layout/fragment_dashboard.xml index 8fdc106292..54cff46390 100644 --- a/app/src/main/res/layout/fragment_dashboard.xml +++ b/app/src/main/res/layout/fragment_dashboard.xml @@ -107,22 +107,77 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/btnMarketplace"> - + android:layout_height="72dp"> + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_dashboard_card_default.xml b/app/src/main/res/layout/item_dashboard_card_default.xml index 995e62410d..d36b804828 100644 --- a/app/src/main/res/layout/item_dashboard_card_default.xml +++ b/app/src/main/res/layout/item_dashboard_card_default.xml @@ -71,4 +71,14 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_desktop_archive.xml b/app/src/main/res/layout/item_desktop_archive.xml index 81876c71b8..0ed2e31fdc 100644 --- a/app/src/main/res/layout/item_desktop_archive.xml +++ b/app/src/main/res/layout/item_desktop_archive.xml @@ -43,4 +43,14 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_desktop_page_task.xml b/app/src/main/res/layout/item_desktop_page_task.xml index 3b3b99ebb4..767c0eab8f 100644 --- a/app/src/main/res/layout/item_desktop_page_task.xml +++ b/app/src/main/res/layout/item_desktop_page_task.xml @@ -70,4 +70,14 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_desktop_page_without_icon.xml b/app/src/main/res/layout/item_desktop_page_without_icon.xml index 756951c278..3c20cb7825 100644 --- a/app/src/main/res/layout/item_desktop_page_without_icon.xml +++ b/app/src/main/res/layout/item_desktop_page_without_icon.xml @@ -64,4 +64,14 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_desktop_set_without_icon.xml b/app/src/main/res/layout/item_desktop_set_without_icon.xml index 7cd0d116aa..88eab65d30 100644 --- a/app/src/main/res/layout/item_desktop_set_without_icon.xml +++ b/app/src/main/res/layout/item_desktop_set_without_icon.xml @@ -64,4 +64,14 @@ + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 020c433992..cf7318f534 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -235,5 +235,6 @@ Do the computation of an expensive paragraph of text on a background thread: %1$d selected blocks Select all Unselect all (%1$d) + Put back diff --git a/app/src/main/res/xml/fragment_dashboard_scene.xml b/app/src/main/res/xml/fragment_dashboard_scene.xml index 7cd250ada4..1675c90ff8 100644 --- a/app/src/main/res/xml/fragment_dashboard_scene.xml +++ b/app/src/main/res/xml/fragment_dashboard_scene.xml @@ -8,66 +8,66 @@ android:layout_width="0dp" android:layout_height="0dp" android:layout_marginTop="58dp" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toTopOf="@+id/selectionBottomToolbar" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/btnMarketplace" /> + app:layout_constraintTop_toTopOf="parent"> + app:layout_constraintTop_toBottomOf="@+id/tvGreeting"> + app:layout_constraintEnd_toStartOf="@+id/btnMarketplace" + app:layout_constraintTop_toBottomOf="@+id/avatarContainer"> + app:layout_constraintTop_toBottomOf="@+id/avatarContainer"> + android:layout_marginTop="50dp" + app:layout_constraintStart_toEndOf="@+id/btnMarketplace" + app:layout_constraintTop_toBottomOf="@+id/avatarContainer"> @@ -79,67 +79,67 @@ android:id="@+id/bottomSheet" android:layout_width="0dp" android:layout_height="0dp" - app:layout_constraintBottom_toBottomOf="parent" + android:layout_marginTop="16dp" + app:layout_constraintBottom_toTopOf="@+id/selectionBottomToolbar" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/ivSettings" - android:layout_marginTop="16dp" /> + app:layout_constraintTop_toBottomOf="@+id/ivSettings" /> + app:layout_constraintTop_toTopOf="parent"> + app:layout_constraintTop_toBottomOf="@+id/tvGreeting"> + app:layout_constraintEnd_toStartOf="@+id/btnMarketplace" + app:layout_constraintTop_toBottomOf="@+id/avatarContainer"> + app:layout_constraintTop_toBottomOf="@+id/avatarContainer"> + android:layout_marginTop="16dp" + app:layout_constraintStart_toEndOf="@+id/btnMarketplace" + app:layout_constraintTop_toBottomOf="@+id/avatarContainer"> diff --git a/core-ui/src/main/res/drawable/ic_bin_selection.xml b/core-ui/src/main/res/drawable/ic_bin_selection.xml new file mode 100644 index 0000000000..bb857e050c --- /dev/null +++ b/core-ui/src/main/res/drawable/ic_bin_selection.xml @@ -0,0 +1,14 @@ + + + + diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataRepository.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataRepository.kt index c835aaf2ed..90cdc06737 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataRepository.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataRepository.kt @@ -462,4 +462,14 @@ class BlockDataRepository( ctx: Id, isArchived: Boolean ): Payload = factory.remote.setObjectIsArchived(ctx = ctx, isArchived = isArchived) + + override fun setObjectListIsArchived( + targets: List, + isArchived: Boolean + ) = factory.remote.setObjectListIsArchived( + targets = targets, + isArchived = isArchived + ) + + override fun deleteObjects(targets: List) = factory.remote.deleteObjects(targets = targets) } \ No newline at end of file diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataStore.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataStore.kt index 82ef350a6e..0bbaa5e1b7 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataStore.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataStore.kt @@ -172,4 +172,7 @@ interface BlockDataStore { fun setObjectIsFavorite(ctx: Id, isFavorite: Boolean) : Payload fun setObjectIsArchived(ctx: Id, isArchived: Boolean) : Payload + + fun setObjectListIsArchived(targets: List, isArchived: Boolean) + fun deleteObjects(targets: List) } \ No newline at end of file diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemote.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemote.kt index 01f7073448..f3b78dd219 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemote.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemote.kt @@ -172,4 +172,7 @@ interface BlockRemote { fun setObjectIsFavorite(ctx: Id, isFavorite: Boolean) : Payload fun setObjectIsArchived(ctx: Id, isArchived: Boolean) : Payload + + fun setObjectListIsArchived(targets: List, isArchived: Boolean) + fun deleteObjects(targets: List) } \ No newline at end of file diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemoteDataStore.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemoteDataStore.kt index f108cd4bdb..4666aa3253 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemoteDataStore.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemoteDataStore.kt @@ -396,4 +396,14 @@ class BlockRemoteDataStore(private val remote: BlockRemote) : BlockDataStore { ctx: Id, isArchived: Boolean ): Payload = remote.setObjectIsArchived(ctx = ctx, isArchived = isArchived) + + override fun setObjectListIsArchived( + targets: List, + isArchived: Boolean + ) = remote.setObjectListIsArchived( + targets = targets, + isArchived = isArchived + ) + + override fun deleteObjects(targets: List) = remote.deleteObjects(targets = targets) } \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/block/repo/BlockRepository.kt b/domain/src/main/java/com/anytypeio/anytype/domain/block/repo/BlockRepository.kt index 07bd68810b..63c3eaa853 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/block/repo/BlockRepository.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/block/repo/BlockRepository.kt @@ -226,4 +226,7 @@ interface BlockRepository { fun setObjectIsFavorite(ctx: Id, isFavorite: Boolean) : Payload fun setObjectIsArchived(ctx: Id, isArchived: Boolean) : Payload + fun setObjectListIsArchived(targets: List, isArchived: Boolean) + + fun deleteObjects(targets: List) } \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/objects/DeleteObjects.kt b/domain/src/main/java/com/anytypeio/anytype/domain/objects/DeleteObjects.kt new file mode 100644 index 0000000000..9cdfde3369 --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/objects/DeleteObjects.kt @@ -0,0 +1,24 @@ +package com.anytypeio.anytype.domain.objects + +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.block.repo.BlockRepository + +/** + * Use-case for deleting objects. + * @see SetObjectIsArchived + */ +class DeleteObjects( + private val repo: BlockRepository +) : BaseUseCase() { + + override suspend fun run(params: Params) = safe { + repo.deleteObjects(params.targets) + } + + /** + * @property [targets] id of the objects to delete. + */ + class Params(val targets: List) +} \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/objects/SetObjectListIsArchived.kt b/domain/src/main/java/com/anytypeio/anytype/domain/objects/SetObjectListIsArchived.kt new file mode 100644 index 0000000000..85f3fb14c6 --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/objects/SetObjectListIsArchived.kt @@ -0,0 +1,30 @@ +package com.anytypeio.anytype.domain.objects + +import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.domain.base.BaseUseCase +import com.anytypeio.anytype.domain.block.repo.BlockRepository + +/** + * Use-case for archiving (or restoring from archive) a list of objects. + */ +class SetObjectListIsArchived( + private val repo: BlockRepository +) : BaseUseCase() { + + override suspend fun run(params: Params) = safe { + repo.setObjectListIsArchived( + isArchived = params.isArchived, + targets = params.targets + ) + } + + + /** + * Params for archiving a list of objects. + * @property [targets] id of the objects to archive/restore. + */ + data class Params( + val targets: List, + val isArchived: Boolean + ) +} \ No newline at end of file diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/block/BlockMiddleware.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/block/BlockMiddleware.kt index 513e97e2af..492e6336fa 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/block/BlockMiddleware.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/block/BlockMiddleware.kt @@ -423,4 +423,16 @@ class BlockMiddleware( ctx: Id, isArchived: Boolean ): Payload = middleware.setObjectIsArchived(ctx = ctx, isArchived = isArchived) + + override fun deleteObjects(targets: List) = middleware.deleteObjects( + targets = targets + ) + + override fun setObjectListIsArchived( + targets: List, + isArchived: Boolean + ) = middleware.setObjectListIsArchived( + targets = targets, + isArchived = isArchived + ) } \ No newline at end of file diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt index 1e0398ac81..349db17b36 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt @@ -1442,4 +1442,24 @@ class Middleware( if (BuildConfig.DEBUG) logResponse(response) return response.event.toPayload() } + + fun setObjectListIsArchived( + targets: List, + isArchived: Boolean + ) { + val request = Rpc.ObjectList.Set.IsArchived.Request( + objectIds = targets, + isArchived = isArchived, + ) + if (BuildConfig.DEBUG) logRequest(request) + val response = service.objectListSetIsArchived(request) + if (BuildConfig.DEBUG) logResponse(response) + } + + fun deleteObjects(targets: List) { + val request = Rpc.ObjectList.Delete.Request(objectIds = targets) + if (BuildConfig.DEBUG) logRequest(request) + val response = service.objectListDelete(request) + if (BuildConfig.DEBUG) logResponse(response) + } } \ No newline at end of file diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt index 08b4ec91d9..dd118b428c 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt @@ -197,4 +197,10 @@ interface MiddlewareService { @Throws(Exception::class) fun objectSetIsArchived(request: Object.SetIsArchived.Request): Object.SetIsArchived.Response + + @Throws(Exception::class) + fun objectListSetIsArchived(request: ObjectList.Set.IsArchived.Request): ObjectList.Set.IsArchived.Response + + @Throws(Exception::class) + fun objectListDelete(request: ObjectList.Delete.Request): ObjectList.Delete.Response } \ No newline at end of file diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt index 276ae7ab99..d1dbc7ecbb 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt @@ -782,4 +782,30 @@ class MiddlewareServiceImplementation : MiddlewareService { return response } } + + override fun objectListSetIsArchived(request: ObjectList.Set.IsArchived.Request): ObjectList.Set.IsArchived.Response { + val encoded = Service.objectListSetIsArchived( + ObjectList.Set.IsArchived.Request.ADAPTER.encode(request) + ) + val response = ObjectList.Set.IsArchived.Response.ADAPTER.decode(encoded) + val error = response.error + if (error != null && error.code != ObjectList.Set.IsArchived.Response.Error.Code.NULL) { + throw Exception(error.description) + } else { + return response + } + } + + override fun objectListDelete(request: ObjectList.Delete.Request): ObjectList.Delete.Response { + val encoded = Service.objectListDelete( + ObjectList.Delete.Request.ADAPTER.encode(request) + ) + val response = ObjectList.Delete.Response.ADAPTER.decode(encoded) + val error = response.error + if (error != null && error.code != ObjectList.Delete.Response.Error.Code.NULL) { + throw Exception(error.description) + } else { + return response + } + } } \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/DashboardView.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/DashboardView.kt index 3ed76cf26f..5ab01a2388 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/DashboardView.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/DashboardView.kt @@ -9,6 +9,7 @@ sealed class DashboardView { abstract val id: Id abstract val isArchived: Boolean + abstract val isSelected: Boolean abstract val isLoading: Boolean data class Profile( @@ -16,6 +17,7 @@ sealed class DashboardView { val name: String, val avatar: Url? = null, override val isArchived: Boolean = false, + override val isSelected: Boolean = false, override val isLoading: Boolean = false ) : DashboardView() @@ -30,6 +32,7 @@ sealed class DashboardView { val type: String? = null, val done: Boolean? = null, override val isArchived: Boolean, + override val isSelected: Boolean = false, override val isLoading: Boolean = false, val icon: ObjectIcon = ObjectIcon.None ) : DashboardView() { @@ -41,6 +44,7 @@ sealed class DashboardView { val target: Id, val title: String, override val isArchived: Boolean = false, + override val isSelected: Boolean = false, override val isLoading: Boolean = false ) : DashboardView() @@ -49,6 +53,7 @@ sealed class DashboardView { val target: Id, val title: String? = null, override val isArchived: Boolean, + override val isSelected: Boolean = false, override val isLoading: Boolean = false, val icon: ObjectIcon = ObjectIcon.None ) : DashboardView() diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardViewModel.kt index eae5c25ac8..61c0f637c1 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardViewModel.kt @@ -23,10 +23,13 @@ import com.anytypeio.anytype.domain.base.BaseUseCase import com.anytypeio.anytype.domain.block.interactor.Move import com.anytypeio.anytype.domain.config.GetConfig import com.anytypeio.anytype.domain.config.GetDebugSettings -import com.anytypeio.anytype.domain.dashboard.interactor.* +import com.anytypeio.anytype.domain.dashboard.interactor.CloseDashboard +import com.anytypeio.anytype.domain.dashboard.interactor.OpenDashboard import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects import com.anytypeio.anytype.domain.event.interactor.InterceptEvents import com.anytypeio.anytype.domain.misc.UrlBuilder +import com.anytypeio.anytype.domain.objects.DeleteObjects +import com.anytypeio.anytype.domain.objects.SetObjectListIsArchived import com.anytypeio.anytype.domain.page.CreatePage import com.anytypeio.anytype.presentation.BuildConfig import com.anytypeio.anytype.presentation.dashboard.HomeDashboardStateMachine.Interactor @@ -55,7 +58,9 @@ class HomeDashboardViewModel( private val getDebugSettings: GetDebugSettings, private val analytics: Analytics, private val searchObjects: SearchObjects, - private val urlBuilder: UrlBuilder + private val urlBuilder: UrlBuilder, + private val setObjectListIsArchived: SetObjectListIsArchived, + private val deleteObjects: DeleteObjects ) : ViewStateViewModel(), HomeDashboardEventConverter by eventConverter, SupportNavigation> { @@ -80,6 +85,9 @@ class HomeDashboardViewModel( val inbox = MutableStateFlow(emptyList()) val sets = MutableStateFlow(emptyList()) + val mode = MutableStateFlow(Mode.DEFAULT) + val count = MutableStateFlow(0) + private val views: List get() = stateData.value?.blocks ?: emptyList() @@ -303,24 +311,27 @@ class HomeDashboardViewModel( fun onTabObjectClicked(target: Id, isLoading: Boolean, tab: TAB = TAB.FAVOURITE) { if (!isLoading) { - val view = when (tab) { - TAB.FAVOURITE -> views.find { it is DashboardView.Document && it.target == target } - TAB.RECENT -> recent.value.find { it is DashboardView.Document && it.target == target } - TAB.ARCHIVE -> archived.value.find { it.target == target } - else -> null - } - if (view is DashboardView.Document && supportedLayouts.contains(view.layout)) { - if (view.type != ObjectType.TEMPLATE_URL) { - if (view.layout == ObjectType.Layout.SET) { - proceedWithOpeningObjectSet(target) + if (tab == TAB.ARCHIVE) { + proceedWithClickInArchiveTab(target) + } else { + val view = when (tab) { + TAB.FAVOURITE -> views.find { it is DashboardView.Document && it.target == target } + TAB.RECENT -> recent.value.find { it is DashboardView.Document && it.target == target } + else -> null + } + if (view is DashboardView.Document && supportedLayouts.contains(view.layout)) { + if (view.type != ObjectType.TEMPLATE_URL) { + if (view.layout == ObjectType.Layout.SET) { + proceedWithOpeningObjectSet(target) + } else { + proceedWithOpeningDocument(target) + } } else { - proceedWithOpeningDocument(target) + toast("Can't open a template on Android. Coming soon") } } else { - toast("Can't open a template on Android. Coming soon") + toast("Currently unsupported layout on Android") } - } else { - toast("Currently unsupported layout on Android") } } else { toast("This object is still syncing.") @@ -528,6 +539,83 @@ class HomeDashboardViewModel( } } + //region BIN SELECTION + + private fun proceedWithClickInArchiveTab(target: Id) { + if (mode.value == Mode.DEFAULT) mode.value = Mode.SELECTION + proceedWithTogglingSelectionForTarget(target) + } + + private fun proceedWithTogglingSelectionForTarget(target: Id) { + val updatedViews = archived.value.map { obj -> + if (obj.id == target) { + obj.copy(isSelected = !obj.isSelected) + } else { + obj + } + } + val updatedCount = updatedViews.count { it.isSelected } + + archived.value = updatedViews + count.value = updatedCount + + if (updatedCount == 0) { + mode.value = Mode.DEFAULT + } + } + + fun onSelectAllClicked() { + archived.value = archived.value.map { obj -> obj.copy(isSelected = true) } + count.value = archived.value.size + } + + fun onCancelSelectionClicked() { + mode.value = Mode.DEFAULT + archived.value = archived.value.map { obj -> obj.copy(isSelected = false) } + count.value = 0 + } + + fun onPutBackClicked() { + viewModelScope.launch { + mode.value = Mode.DEFAULT + setObjectListIsArchived( + SetObjectListIsArchived.Params( + targets = archived.value.filter { it.isSelected }.map { it.id }, + isArchived = false + ) + ).process( + failure = { e -> + Timber.e(e, "Error while restoring objects from archive") + proceedWithArchivedObjectSearch() + }, + success = { + proceedWithArchivedObjectSearch() + } + ) + } + } + + fun onDeleteObjectsClicked() { + viewModelScope.launch { + mode.value = Mode.DEFAULT + deleteObjects( + DeleteObjects.Params( + targets = archived.value.filter { it.isSelected }.map { it.id } + ) + ).process( + failure = { e -> + Timber.e(e, "Error while deleting objects") + proceedWithArchivedObjectSearch() + }, + success = { + proceedWithArchivedObjectSearch() + } + ) + } + } + + //endregion + /** * Represents movements of blocks during block dragging action. * @param subject id of the block being dragged @@ -553,4 +641,6 @@ class HomeDashboardViewModel( } enum class TAB { FAVOURITE, RECENT, INBOX, SETS, ARCHIVE } + + enum class Mode { DEFAULT, SELECTION } } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardViewModelFactory.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardViewModelFactory.kt index e9b15dfd00..fd3ca47435 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardViewModelFactory.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardViewModelFactory.kt @@ -11,6 +11,8 @@ import com.anytypeio.anytype.domain.dashboard.interactor.* import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects import com.anytypeio.anytype.domain.event.interactor.InterceptEvents import com.anytypeio.anytype.domain.misc.UrlBuilder +import com.anytypeio.anytype.domain.objects.DeleteObjects +import com.anytypeio.anytype.domain.objects.SetObjectListIsArchived import com.anytypeio.anytype.domain.page.CreatePage class HomeDashboardViewModelFactory( @@ -25,7 +27,9 @@ class HomeDashboardViewModelFactory( private val getDebugSettings: GetDebugSettings, private val analytics: Analytics, private val searchObjects: SearchObjects, - private val urlBuilder: UrlBuilder + private val urlBuilder: UrlBuilder, + private val setObjectListIsArchived: SetObjectListIsArchived, + private val deleteObjects: DeleteObjects ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") @@ -42,7 +46,9 @@ class HomeDashboardViewModelFactory( getDebugSettings = getDebugSettings, analytics = analytics, searchObjects = searchObjects, - urlBuilder = urlBuilder + urlBuilder = urlBuilder, + deleteObjects = deleteObjects, + setObjectListIsArchived = setObjectListIsArchived ) as T } } \ No newline at end of file diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/dashboard/DashboardBinTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/dashboard/DashboardBinTest.kt new file mode 100644 index 0000000000..c92cb1617c --- /dev/null +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/dashboard/DashboardBinTest.kt @@ -0,0 +1,403 @@ +package com.anytypeio.anytype.presentation.dashboard + +import com.anytypeio.anytype.core_models.* +import com.anytypeio.anytype.domain.base.Either +import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects +import com.anytypeio.anytype.domain.event.interactor.InterceptEvents +import com.anytypeio.anytype.presentation.search.ObjectSearchConstants +import org.junit.Before +import org.junit.Test +import org.mockito.MockitoAnnotations +import kotlin.test.assertEquals + +class DashboardBinTest : DashboardTestSetup() { + + private val objectIds = listOf( + MockDataFactory.randomUuid(), + MockDataFactory.randomUuid(), + MockDataFactory.randomUuid(), + MockDataFactory.randomUuid(), + ) + + private val objects : List> = listOf( + mapOf( + Relations.ID to objectIds[0] + ), + mapOf( + Relations.ID to objectIds[1] + ), + mapOf( + Relations.ID to objectIds[2] + ), + mapOf( + Relations.ID to objectIds[3] + ) + ) + + @Before + fun setup() { + MockitoAnnotations.openMocks(this) + } + + @Test + fun `should enter select mode when block is clicked in bin tab`() { + + // SETUP + + val dashboard = Block( + id = config.home, + content = Block.Content.Smart(SmartBlockType.HOME), + children = emptyList(), + fields = Block.Fields.empty() + ) + + stubGetConfig(Either.Right(config)) + + stubObserveEvents(params = InterceptEvents.Params(context = config.home)) + + stubOpenDashboard( + payload = Payload( + context = config.home, + events = listOf( + Event.Command.ShowObject( + root = config.home, + context = config.home, + blocks = listOf(dashboard), + type = SmartBlockType.HOME + ) + ) + ) + ) + + stubSearchObjects( + params = SearchObjects.Params( + filters = ObjectSearchConstants.filterTabArchive, + sorts = ObjectSearchConstants.sortTabArchive + ), + objects = objects + ) + + vm = buildViewModel() + + vm.onViewCreated() + + // TESTING + + val expectedBeforeSelection = listOf( + DashboardView.Document( + id = objectIds[0], + isArchived = true, + target = objectIds[0] + ), + DashboardView.Document( + id = objectIds[1], + isArchived = true, + target = objectIds[1] + ), + DashboardView.Document( + id = objectIds[2], + isArchived = true, + target = objectIds[2] + ), + DashboardView.Document( + id = objectIds[3], + isArchived = true, + target = objectIds[3] + ) + ) + + assertEquals( + expected = expectedBeforeSelection, + actual = vm.archived.value + ) + + assertEquals( + expected = HomeDashboardViewModel.Mode.DEFAULT, + actual = vm.mode.value + ) + + vm.onTabObjectClicked( + target = objectIds[0], + tab = HomeDashboardViewModel.TAB.ARCHIVE, + isLoading = false + ) + + val expectedAfterSelection = expectedBeforeSelection.map { obj -> + if (obj.id == objectIds[0]) + obj.copy(isSelected = true) + else + obj + } + + assertEquals( + expected = expectedAfterSelection, + actual = vm.archived.value + ) + + assertEquals( + expected = HomeDashboardViewModel.Mode.SELECTION, + actual = vm.mode.value + ) + } + + @Test + fun `should toggle selection on object click in bin tab`() { + + // SETUP + + val dashboard = Block( + id = config.home, + content = Block.Content.Smart(SmartBlockType.HOME), + children = emptyList(), + fields = Block.Fields.empty() + ) + + stubGetConfig(Either.Right(config)) + + stubObserveEvents(params = InterceptEvents.Params(context = config.home)) + + stubOpenDashboard( + payload = Payload( + context = config.home, + events = listOf( + Event.Command.ShowObject( + root = config.home, + context = config.home, + blocks = listOf(dashboard), + type = SmartBlockType.HOME + ) + ) + ) + ) + + stubSearchObjects( + params = SearchObjects.Params( + filters = ObjectSearchConstants.filterTabArchive, + sorts = ObjectSearchConstants.sortTabArchive + ), + objects = objects + ) + + vm = buildViewModel() + + vm.onViewCreated() + + // TESTING + + val expectedBeforeSelection = listOf( + DashboardView.Document( + id = objectIds[0], + isArchived = true, + target = objectIds[0] + ), + DashboardView.Document( + id = objectIds[1], + isArchived = true, + target = objectIds[1] + ), + DashboardView.Document( + id = objectIds[2], + isArchived = true, + target = objectIds[2] + ), + DashboardView.Document( + id = objectIds[3], + isArchived = true, + target = objectIds[3] + ) + ) + + assertEquals( + expected = expectedBeforeSelection, + actual = vm.archived.value + ) + + assertEquals( + expected = HomeDashboardViewModel.Mode.DEFAULT, + actual = vm.mode.value + ) + + // Clicking on the first object, in order to enter select mode + + vm.onTabObjectClicked( + target = objectIds[0], + tab = HomeDashboardViewModel.TAB.ARCHIVE, + isLoading = false + ) + + // Clicking on the second object, in order select it + + vm.onTabObjectClicked( + target = objectIds[1], + tab = HomeDashboardViewModel.TAB.ARCHIVE, + isLoading = false + ) + + // Checking that two object are selected + + val expectedAfterSelection = expectedBeforeSelection.map { obj -> + if (obj.id == objectIds[0] || obj.id == objectIds[1]) + obj.copy(isSelected = true) + else + obj + } + + assertEquals( + expected = expectedAfterSelection, + actual = vm.archived.value + ) + + // Clicking on the second object, in order unselect it + + vm.onTabObjectClicked( + target = objectIds[1], + tab = HomeDashboardViewModel.TAB.ARCHIVE, + isLoading = false + ) + + // Checking that only the first object is selected now + + val expectedAfterUnselect = expectedBeforeSelection.map { obj -> + if (obj.id == objectIds[0]) + obj.copy(isSelected = true) + else + obj + } + + assertEquals( + expected = expectedAfterUnselect, + actual = vm.archived.value + ) + } + + @Test + fun `should exit select mode if no object is selected in bin tab`() { + + // SETUP + + val dashboard = Block( + id = config.home, + content = Block.Content.Smart(SmartBlockType.HOME), + children = emptyList(), + fields = Block.Fields.empty() + ) + + stubGetConfig(Either.Right(config)) + + stubObserveEvents(params = InterceptEvents.Params(context = config.home)) + + stubOpenDashboard( + payload = Payload( + context = config.home, + events = listOf( + Event.Command.ShowObject( + root = config.home, + context = config.home, + blocks = listOf(dashboard), + type = SmartBlockType.HOME + ) + ) + ) + ) + + stubSearchObjects( + params = SearchObjects.Params( + filters = ObjectSearchConstants.filterTabArchive, + sorts = ObjectSearchConstants.sortTabArchive + ), + objects = objects + ) + + vm = buildViewModel() + + vm.onViewCreated() + + // TESTING + + val expectedBeforeSelection = listOf( + DashboardView.Document( + id = objectIds[0], + isArchived = true, + target = objectIds[0] + ), + DashboardView.Document( + id = objectIds[1], + isArchived = true, + target = objectIds[1] + ), + DashboardView.Document( + id = objectIds[2], + isArchived = true, + target = objectIds[2] + ), + DashboardView.Document( + id = objectIds[3], + isArchived = true, + target = objectIds[3] + ) + ) + + assertEquals( + expected = expectedBeforeSelection, + actual = vm.archived.value + ) + + assertEquals( + expected = HomeDashboardViewModel.Mode.DEFAULT, + actual = vm.mode.value + ) + + // Clicking on the first object, in order to enter select mode + + vm.onTabObjectClicked( + target = objectIds[0], + tab = HomeDashboardViewModel.TAB.ARCHIVE, + isLoading = false + ) + + // Checking that this first object is selected + + val expectedAfterSelection = expectedBeforeSelection.map { obj -> + if (obj.id == objectIds[0]) + obj.copy(isSelected = true) + else + obj + } + + assertEquals( + expected = expectedAfterSelection, + actual = vm.archived.value + ) + + // Checking that bin tab is now in select mode + + assertEquals( + expected = HomeDashboardViewModel.Mode.SELECTION, + actual = vm.mode.value + ) + + // Clicking on the first object again to unselect it + + vm.onTabObjectClicked( + target = objectIds[0], + tab = HomeDashboardViewModel.TAB.ARCHIVE, + isLoading = false + ) + + // Checking that no object is selected now + + val expectedAfterUnselect = expectedBeforeSelection + + assertEquals( + expected = expectedAfterUnselect, + actual = vm.archived.value + ) + + // Checking that bin tab is not select mode + + assertEquals( + expected = HomeDashboardViewModel.Mode.DEFAULT, + actual = vm.mode.value + ) + } +} \ No newline at end of file diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/dashboard/DashboardTestSetup.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/dashboard/DashboardTestSetup.kt index 40b5b3d2cb..a89fa79d33 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/dashboard/DashboardTestSetup.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/dashboard/DashboardTestSetup.kt @@ -19,6 +19,8 @@ import com.anytypeio.anytype.domain.dashboard.interactor.* import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects import com.anytypeio.anytype.domain.event.interactor.InterceptEvents import com.anytypeio.anytype.domain.misc.UrlBuilder +import com.anytypeio.anytype.domain.objects.DeleteObjects +import com.anytypeio.anytype.domain.objects.SetObjectListIsArchived import com.anytypeio.anytype.domain.page.CreatePage import com.anytypeio.anytype.presentation.util.CoroutinesTestRule import kotlinx.coroutines.flow.Flow @@ -50,6 +52,12 @@ open class DashboardTestSetup { @Mock lateinit var closeDashboard: CloseDashboard + @Mock + lateinit var deleteObjects: DeleteObjects + + @Mock + lateinit var setObjectListIsArchived: SetObjectListIsArchived + @Mock lateinit var createPage: CreatePage @@ -99,7 +107,9 @@ open class DashboardTestSetup { getDebugSettings = getDebugSettings, analytics = analytics, searchObjects = searchObjects, - urlBuilder = builder + urlBuilder = builder, + setObjectListIsArchived = setObjectListIsArchived, + deleteObjects = deleteObjects ) fun stubGetConfig(response: Either.Right) { @@ -159,4 +169,13 @@ open class DashboardTestSetup { } } } + + fun stubSearchObjects( + params: SearchObjects.Params, + objects: List> = emptyList() + ) { + searchObjects.stub { + onBlocking { invoke(params) } doReturn Either.Right(objects) + } + } } \ No newline at end of file diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardViewModelTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardViewModelTest.kt index b763947bbe..ac21993ecf 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardViewModelTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardViewModelTest.kt @@ -17,6 +17,8 @@ import com.anytypeio.anytype.domain.dashboard.interactor.* import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects import com.anytypeio.anytype.domain.event.interactor.InterceptEvents import com.anytypeio.anytype.domain.misc.UrlBuilder +import com.anytypeio.anytype.domain.objects.DeleteObjects +import com.anytypeio.anytype.domain.objects.SetObjectListIsArchived import com.anytypeio.anytype.domain.page.CreatePage import com.anytypeio.anytype.presentation.navigation.AppNavigation import com.anytypeio.anytype.presentation.util.CoroutinesTestRule @@ -57,6 +59,12 @@ class HomeDashboardViewModelTest { @Mock lateinit var searchObjects: SearchObjects + @Mock + lateinit var setObjectListIsArchived: SetObjectListIsArchived + + @Mock + lateinit var deleteObjects: DeleteObjects + @Mock lateinit var interceptEvents: InterceptEvents @@ -106,6 +114,8 @@ class HomeDashboardViewModelTest { getDebugSettings = getDebugSettings, analytics = analytics, searchObjects = searchObjects, + deleteObjects = deleteObjects, + setObjectListIsArchived = setObjectListIsArchived, urlBuilder = builder ) }