mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-08 05:47:05 +09:00
Dashboard | Feature | Bulk delete & bulk put-back in Bin tab (#1858)
This commit is contained in:
parent
80795f5ff7
commit
d75d6e9c06
30 changed files with 1052 additions and 73 deletions
|
@ -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
|
||||
)
|
||||
}
|
|
@ -195,6 +195,8 @@ class DashboardAdapter(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
holder.bindSelection(data[position].isSelected)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
|
||||
|
@ -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<ImageView>(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<ImageView>(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<TextView>(R.id.tvDocTitle)
|
||||
private val tvSubtitle = itemView.findViewById<TextView>(R.id.tvDocTypeName)
|
||||
private val shimmer = itemView.findViewById<ShimmerFrameLayout>(R.id.shimmer)
|
||||
private val selection = itemView.findViewById<ImageView>(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<TextView>(R.id.tvDocTypeName)
|
||||
private val checkbox = itemView.findViewById<ImageView>(R.id.ivCheckbox)
|
||||
private val shimmer = itemView.findViewById<ShimmerFrameLayout>(R.id.shimmer)
|
||||
private val selection = itemView.findViewById<ImageView>(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<ImageView>(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<TextView>(R.id.tvSetTitle)
|
||||
private val shimmer = itemView.findViewById<ShimmerFrameLayout>(R.id.shimmer)
|
||||
private val selection = itemView.findViewById<ImageView>(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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<State>(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<State>(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() {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -107,22 +107,77 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/btnMarketplace">
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
app:tabRippleColor="@null"
|
||||
android:id="@+id/tabsLayout"
|
||||
<FrameLayout
|
||||
android:id="@+id/topToolbarContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="72dp"
|
||||
android:paddingStart="10dp"
|
||||
android:background="@null"
|
||||
app:tabIndicator="@null"
|
||||
app:tabMinWidth="10dp"
|
||||
app:tabPaddingBottom="@dimen/dp_12"
|
||||
app:tabPaddingEnd="10dp"
|
||||
app:tabPaddingStart="10dp"
|
||||
app:tabPaddingTop="@dimen/dp_12"
|
||||
app:tabSelectedTextColor="@color/black"
|
||||
app:tabTextAppearance="@style/DashboardTabTextStyle"
|
||||
app:tabTextColor="#CC0066C3" />
|
||||
android:layout_height="72dp">
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tabsLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@null"
|
||||
android:paddingStart="10dp"
|
||||
app:tabIndicator="@null"
|
||||
app:tabMinWidth="10dp"
|
||||
app:tabPaddingBottom="@dimen/dp_12"
|
||||
app:tabPaddingEnd="10dp"
|
||||
app:tabPaddingStart="10dp"
|
||||
app:tabPaddingTop="@dimen/dp_12"
|
||||
app:tabRippleColor="@null"
|
||||
app:tabSelectedTextColor="@color/black"
|
||||
app:tabTextAppearance="@style/DashboardTabTextStyle"
|
||||
app:tabTextColor="#CC0066C3" />
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:visibility="gone"
|
||||
android:id="@+id/selectionTopToolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSelectAll"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:fontFamily="@font/inter_regular"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingEnd="20dp"
|
||||
android:text="@string/select_all"
|
||||
android:textColor="@color/black"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:textSize="17sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSelectedCount"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:fontFamily="@font/inter_semibold"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="17sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCancel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:fontFamily="@font/inter_regular"
|
||||
android:gravity="end|center_vertical"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingEnd="20dp"
|
||||
android:text="@string/cancel"
|
||||
android:textColor="@color/black"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:textSize="17sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/dashboardPager"
|
||||
|
@ -132,4 +187,43 @@
|
|||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/selectionBottomToolbar"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/default_toolbar_height"
|
||||
android:orientation="horizontal"
|
||||
android:background="#99F5F5F5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvDelete"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:fontFamily="@font/inter_regular"
|
||||
android:gravity="center"
|
||||
android:paddingStart="20dp"
|
||||
android:background="@drawable/default_ripple"
|
||||
android:paddingEnd="20dp"
|
||||
android:text="@string/delete"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="17sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPutBack"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:fontFamily="@font/inter_regular"
|
||||
android:gravity="center"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingEnd="20dp"
|
||||
android:text="@string/put_back"
|
||||
android:background="@drawable/default_ripple"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="17sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.motion.widget.MotionLayout>
|
|
@ -71,4 +71,14 @@
|
|||
|
||||
</com.facebook.shimmer.ShimmerFrameLayout>
|
||||
|
||||
<ImageView
|
||||
android:visibility="invisible"
|
||||
android:paddingEnd="10dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:layout_gravity="end|bottom"
|
||||
android:id="@+id/ivSelection"
|
||||
android:src="@drawable/ic_bin_selection"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
|
@ -43,4 +43,14 @@
|
|||
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:visibility="invisible"
|
||||
android:paddingEnd="10dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:layout_gravity="end|bottom"
|
||||
android:id="@+id/ivSelection"
|
||||
android:src="@drawable/ic_bin_selection"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
|
@ -70,4 +70,14 @@
|
|||
|
||||
</com.facebook.shimmer.ShimmerFrameLayout>
|
||||
|
||||
<ImageView
|
||||
android:visibility="invisible"
|
||||
android:paddingEnd="10dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:layout_gravity="end|bottom"
|
||||
android:id="@+id/ivSelection"
|
||||
android:src="@drawable/ic_bin_selection"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
|
@ -64,4 +64,14 @@
|
|||
|
||||
</com.facebook.shimmer.ShimmerFrameLayout>
|
||||
|
||||
<ImageView
|
||||
android:visibility="invisible"
|
||||
android:paddingEnd="10dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:layout_gravity="end|bottom"
|
||||
android:id="@+id/ivSelection"
|
||||
android:src="@drawable/ic_bin_selection"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
|
@ -64,4 +64,14 @@
|
|||
|
||||
</com.facebook.shimmer.ShimmerFrameLayout>
|
||||
|
||||
<ImageView
|
||||
android:visibility="invisible"
|
||||
android:paddingEnd="10dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:layout_gravity="end|bottom"
|
||||
android:id="@+id/ivSelection"
|
||||
android:src="@drawable/ic_bin_selection"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
|
@ -235,5 +235,6 @@ Do the computation of an expensive paragraph of text on a background thread:
|
|||
<string name="number_selected_blocks">%1$d selected blocks</string>
|
||||
<string name="select_all">Select all</string>
|
||||
<string name="unselect_all">Unselect all (%1$d)</string>
|
||||
<string name="put_back">Put back</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -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" />
|
||||
<Constraint
|
||||
android:id="@+id/tvGreeting"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:layout_marginTop="70dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
android:layout_marginTop="70dp">
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
<CustomAttribute
|
||||
app:attributeName="textColor"
|
||||
app:customColorValue="@color/white" />
|
||||
</Constraint>
|
||||
<Constraint
|
||||
android:id="@+id/avatarContainer"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="80dp"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tvGreeting"
|
||||
android:layout_marginTop="12dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
android:layout_marginTop="12dp">
|
||||
app:layout_constraintTop_toBottomOf="@+id/tvGreeting">
|
||||
<CustomAttribute
|
||||
app:attributeName="alpha"
|
||||
app:customFloatValue="1.0" />
|
||||
</Constraint>
|
||||
<Constraint
|
||||
android:id="@+id/btnSearch"
|
||||
app:layout_constraintEnd_toStartOf="@+id/btnMarketplace"
|
||||
android:layout_width="52dp"
|
||||
android:layout_height="52dp"
|
||||
android:layout_marginTop="50dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
app:layout_constraintTop_toBottomOf="@+id/avatarContainer"
|
||||
android:layout_marginTop="50dp">
|
||||
app:layout_constraintEnd_toStartOf="@+id/btnMarketplace"
|
||||
app:layout_constraintTop_toBottomOf="@+id/avatarContainer">
|
||||
<CustomAttribute
|
||||
app:attributeName="alpha"
|
||||
app:customFloatValue="1.0" />
|
||||
</Constraint>
|
||||
<Constraint
|
||||
android:id="@+id/btnMarketplace"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_width="52dp"
|
||||
android:layout_height="52dp"
|
||||
app:layout_constraintTop_toBottomOf="@+id/avatarContainer"
|
||||
android:layout_marginTop="50dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
android:layout_marginTop="50dp">
|
||||
app:layout_constraintTop_toBottomOf="@+id/avatarContainer">
|
||||
<CustomAttribute
|
||||
app:attributeName="alpha"
|
||||
app:customFloatValue="1.0" />
|
||||
</Constraint>
|
||||
<Constraint
|
||||
android:id="@+id/btnAddDoc"
|
||||
app:layout_constraintStart_toEndOf="@+id/btnMarketplace"
|
||||
android:layout_width="52dp"
|
||||
android:layout_height="52dp"
|
||||
app:layout_constraintTop_toBottomOf="@+id/avatarContainer"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginTop="50dp">
|
||||
android:layout_marginTop="50dp"
|
||||
app:layout_constraintStart_toEndOf="@+id/btnMarketplace"
|
||||
app:layout_constraintTop_toBottomOf="@+id/avatarContainer">
|
||||
<CustomAttribute
|
||||
app:attributeName="alpha"
|
||||
app:customFloatValue="1.0" />
|
||||
|
@ -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" />
|
||||
<Constraint
|
||||
android:id="@+id/tvGreeting"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:layout_marginTop="70dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
android:layout_marginTop="70dp">
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
<CustomAttribute
|
||||
app:attributeName="textColor"
|
||||
app:customColorValue="#0066C3" />
|
||||
</Constraint>
|
||||
<Constraint
|
||||
android:id="@+id/avatarContainer"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="80dp"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tvGreeting"
|
||||
android:layout_marginTop="12dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
android:layout_marginTop="12dp">
|
||||
app:layout_constraintTop_toBottomOf="@+id/tvGreeting">
|
||||
<CustomAttribute
|
||||
app:attributeName="alpha"
|
||||
app:customFloatValue="0.0" />
|
||||
</Constraint>
|
||||
<Constraint
|
||||
android:id="@+id/btnSearch"
|
||||
app:layout_constraintEnd_toStartOf="@+id/btnMarketplace"
|
||||
android:layout_width="52dp"
|
||||
android:layout_height="52dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
app:layout_constraintTop_toBottomOf="@+id/avatarContainer"
|
||||
android:layout_marginTop="16dp">
|
||||
app:layout_constraintEnd_toStartOf="@+id/btnMarketplace"
|
||||
app:layout_constraintTop_toBottomOf="@+id/avatarContainer">
|
||||
<CustomAttribute
|
||||
app:attributeName="alpha"
|
||||
app:customFloatValue="0.0" />
|
||||
</Constraint>
|
||||
<Constraint
|
||||
android:id="@+id/btnMarketplace"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_width="52dp"
|
||||
android:layout_height="52dp"
|
||||
app:layout_constraintTop_toBottomOf="@+id/avatarContainer"
|
||||
android:layout_marginTop="16dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
android:layout_marginTop="16dp">
|
||||
app:layout_constraintTop_toBottomOf="@+id/avatarContainer">
|
||||
<CustomAttribute
|
||||
app:attributeName="alpha"
|
||||
app:customFloatValue="0.0" />
|
||||
</Constraint>
|
||||
<Constraint
|
||||
android:id="@+id/btnAddDoc"
|
||||
app:layout_constraintStart_toEndOf="@+id/btnMarketplace"
|
||||
android:layout_width="52dp"
|
||||
android:layout_height="52dp"
|
||||
app:layout_constraintTop_toBottomOf="@+id/avatarContainer"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginTop="16dp">
|
||||
android:layout_marginTop="16dp"
|
||||
app:layout_constraintStart_toEndOf="@+id/btnMarketplace"
|
||||
app:layout_constraintTop_toBottomOf="@+id/avatarContainer">
|
||||
<CustomAttribute
|
||||
app:attributeName="alpha"
|
||||
app:customFloatValue="0.0" />
|
||||
|
|
14
core-ui/src/main/res/drawable/ic_bin_selection.xml
Normal file
14
core-ui/src/main/res/drawable/ic_bin_selection.xml
Normal file
|
@ -0,0 +1,14 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
<path
|
||||
android:pathData="M0,10C0,4.4771 4.4771,0 10,0C15.5228,0 20,4.4771 20,10C20,15.5228 15.5228,20 10,20C4.4771,20 0,15.5228 0,10Z"
|
||||
android:fillColor="#000000"/>
|
||||
<path
|
||||
android:pathData="M6,9.5L9.5,13.5L14.5,5.5"
|
||||
android:strokeWidth="1.35"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"/>
|
||||
</vector>
|
|
@ -462,4 +462,14 @@ class BlockDataRepository(
|
|||
ctx: Id,
|
||||
isArchived: Boolean
|
||||
): Payload = factory.remote.setObjectIsArchived(ctx = ctx, isArchived = isArchived)
|
||||
|
||||
override fun setObjectListIsArchived(
|
||||
targets: List<Id>,
|
||||
isArchived: Boolean
|
||||
) = factory.remote.setObjectListIsArchived(
|
||||
targets = targets,
|
||||
isArchived = isArchived
|
||||
)
|
||||
|
||||
override fun deleteObjects(targets: List<Id>) = factory.remote.deleteObjects(targets = targets)
|
||||
}
|
|
@ -172,4 +172,7 @@ interface BlockDataStore {
|
|||
|
||||
fun setObjectIsFavorite(ctx: Id, isFavorite: Boolean) : Payload
|
||||
fun setObjectIsArchived(ctx: Id, isArchived: Boolean) : Payload
|
||||
|
||||
fun setObjectListIsArchived(targets: List<Id>, isArchived: Boolean)
|
||||
fun deleteObjects(targets: List<Id>)
|
||||
}
|
|
@ -172,4 +172,7 @@ interface BlockRemote {
|
|||
|
||||
fun setObjectIsFavorite(ctx: Id, isFavorite: Boolean) : Payload
|
||||
fun setObjectIsArchived(ctx: Id, isArchived: Boolean) : Payload
|
||||
|
||||
fun setObjectListIsArchived(targets: List<Id>, isArchived: Boolean)
|
||||
fun deleteObjects(targets: List<Id>)
|
||||
}
|
|
@ -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<Id>,
|
||||
isArchived: Boolean
|
||||
) = remote.setObjectListIsArchived(
|
||||
targets = targets,
|
||||
isArchived = isArchived
|
||||
)
|
||||
|
||||
override fun deleteObjects(targets: List<Id>) = remote.deleteObjects(targets = targets)
|
||||
}
|
|
@ -226,4 +226,7 @@ interface BlockRepository {
|
|||
|
||||
fun setObjectIsFavorite(ctx: Id, isFavorite: Boolean) : Payload
|
||||
fun setObjectIsArchived(ctx: Id, isArchived: Boolean) : Payload
|
||||
fun setObjectListIsArchived(targets: List<Id>, isArchived: Boolean)
|
||||
|
||||
fun deleteObjects(targets: List<Id>)
|
||||
}
|
|
@ -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<Unit, DeleteObjects.Params>() {
|
||||
|
||||
override suspend fun run(params: Params) = safe {
|
||||
repo.deleteObjects(params.targets)
|
||||
}
|
||||
|
||||
/**
|
||||
* @property [targets] id of the objects to delete.
|
||||
*/
|
||||
class Params(val targets: List<Id>)
|
||||
}
|
|
@ -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<Unit, SetObjectListIsArchived.Params>() {
|
||||
|
||||
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<Id>,
|
||||
val isArchived: Boolean
|
||||
)
|
||||
}
|
|
@ -423,4 +423,16 @@ class BlockMiddleware(
|
|||
ctx: Id,
|
||||
isArchived: Boolean
|
||||
): Payload = middleware.setObjectIsArchived(ctx = ctx, isArchived = isArchived)
|
||||
|
||||
override fun deleteObjects(targets: List<Id>) = middleware.deleteObjects(
|
||||
targets = targets
|
||||
)
|
||||
|
||||
override fun setObjectListIsArchived(
|
||||
targets: List<Id>,
|
||||
isArchived: Boolean
|
||||
) = middleware.setObjectListIsArchived(
|
||||
targets = targets,
|
||||
isArchived = isArchived
|
||||
)
|
||||
}
|
|
@ -1442,4 +1442,24 @@ class Middleware(
|
|||
if (BuildConfig.DEBUG) logResponse(response)
|
||||
return response.event.toPayload()
|
||||
}
|
||||
|
||||
fun setObjectListIsArchived(
|
||||
targets: List<Id>,
|
||||
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<Id>) {
|
||||
val request = Rpc.ObjectList.Delete.Request(objectIds = targets)
|
||||
if (BuildConfig.DEBUG) logRequest(request)
|
||||
val response = service.objectListDelete(request)
|
||||
if (BuildConfig.DEBUG) logResponse(response)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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<State>(),
|
||||
HomeDashboardEventConverter by eventConverter,
|
||||
SupportNavigation<EventWrapper<AppNavigation.Command>> {
|
||||
|
@ -80,6 +85,9 @@ class HomeDashboardViewModel(
|
|||
val inbox = MutableStateFlow(emptyList<DashboardView>())
|
||||
val sets = MutableStateFlow(emptyList<DashboardView>())
|
||||
|
||||
val mode = MutableStateFlow(Mode.DEFAULT)
|
||||
val count = MutableStateFlow(0)
|
||||
|
||||
private val views: List<DashboardView>
|
||||
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 }
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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<Map<String, Any?>> = 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
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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<Config>) {
|
||||
|
@ -159,4 +169,13 @@ open class DashboardTestSetup {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun stubSearchObjects(
|
||||
params: SearchObjects.Params,
|
||||
objects: List<Map<String, Any?>> = emptyList()
|
||||
) {
|
||||
searchObjects.stub {
|
||||
onBlocking { invoke(params) } doReturn Either.Right(objects)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue