From c51a302051f1ffde3a168cda19a3171333c06df9 Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Tue, 8 Apr 2025 20:15:59 +0200 Subject: [PATCH] DROID-3538 Widgets | Enhancement | Add plus button to system and object-type widgets (#2260) --- .../anytypeio/anytype/ui/home/HomeScreen.kt | 33 ++++++--- .../anytype/ui/home/HomeScreenFragment.kt | 1 + .../ui/widgets/types/DataViewWidget.kt | 7 +- .../anytype/ui/widgets/types/ListWidget.kt | 7 +- .../anytype/ui/widgets/types/TreeWidget.kt | 27 ++++++- .../res/drawable/ic_widget_tree_expand.xml | 2 +- .../res/drawable/ic_widget_system_plus_18.xml | 10 +++ .../interactor/SetObjectListIsFavorite.kt | 3 +- .../presentation/home/HomeScreenViewModel.kt | 71 ++++++++++++++++++- .../home/HomeScreenViewModelTest.kt | 7 +- 10 files changed, 146 insertions(+), 22 deletions(-) create mode 100644 core-ui/src/main/res/drawable/ic_widget_system_plus_18.xml diff --git a/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreen.kt b/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreen.kt index ed54371fb9..4f3cd90b65 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreen.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreen.kt @@ -99,7 +99,8 @@ fun HomeScreen( onSpaceWidgetShareIconClicked: (ObjectWrapper.SpaceView) -> Unit, onSeeAllObjectsClicked: (WidgetView.Gallery) -> Unit, onCreateObjectInsideWidget: (Id) -> Unit, - onCreateDataViewObject: (WidgetId, ViewId?) -> Unit + onCreateDataViewObject: (WidgetId, ViewId?) -> Unit, + onCreateElement: (WidgetView) -> Unit = {} ) { Box(modifier = modifier.fillMaxSize()) { @@ -121,7 +122,8 @@ fun HomeScreen( onSeeAllObjectsClicked = onSeeAllObjectsClicked, onCreateWidget = onCreateWidget, onCreateObjectInsideWidget = onCreateObjectInsideWidget, - onCreateDataViewObject = onCreateDataViewObject + onCreateDataViewObject = onCreateDataViewObject, + onCreateElement = onCreateElement ) AnimatedVisibility( visible = mode is InteractionMode.Edit, @@ -192,7 +194,8 @@ private fun WidgetList( onSeeAllObjectsClicked: (WidgetView.Gallery) -> Unit, onCreateWidget: () -> Unit, onCreateObjectInsideWidget: (Id) -> Unit, - onCreateDataViewObject: (WidgetId, ViewId?) -> Unit + onCreateDataViewObject: (WidgetId, ViewId?) -> Unit, + onCreateElement: (WidgetView) -> Unit = {} ) { val views = remember { mutableStateOf(widgets) } views.value = widgets @@ -315,7 +318,8 @@ private fun WidgetList( onChangeWidgetView = onChangeWidgetView, onToggleExpandedWidgetState = onToggleExpandedWidgetState, onObjectCheckboxClicked = onObjectCheckboxClicked, - onCreateDataViewObject = onCreateDataViewObject + onCreateDataViewObject = onCreateDataViewObject, + onCreateElement = onCreateElement ) } } else { @@ -331,7 +335,8 @@ private fun WidgetList( onChangeWidgetView = onChangeWidgetView, onToggleExpandedWidgetState = onToggleExpandedWidgetState, onObjectCheckboxClicked = onObjectCheckboxClicked, - onCreateDataViewObject = onCreateDataViewObject + onCreateDataViewObject = onCreateDataViewObject, + onCreateElement = onCreateElement ) } } @@ -389,7 +394,8 @@ private fun WidgetList( onWidgetSourceClicked = onWidgetSourceClicked, onWidgetMenuAction = onWidgetMenuAction, onToggleExpandedWidgetState = onToggleExpandedWidgetState, - onObjectCheckboxClicked = onObjectCheckboxClicked + onObjectCheckboxClicked = onObjectCheckboxClicked, + onCreateElement = onCreateElement ) } } else { @@ -403,7 +409,8 @@ private fun WidgetList( onWidgetSourceClicked = onWidgetSourceClicked, onWidgetMenuAction = onWidgetMenuAction, onToggleExpandedWidgetState = onToggleExpandedWidgetState, - onObjectCheckboxClicked = onObjectCheckboxClicked + onObjectCheckboxClicked = onObjectCheckboxClicked, + onCreateElement = onCreateElement ) } } @@ -521,7 +528,8 @@ private fun ListOfObjectsItem( onWidgetSourceClicked: (Widget.Source) -> Unit, onWidgetMenuAction: (WidgetId, DropDownMenuAction) -> Unit, onToggleExpandedWidgetState: (WidgetId) -> Unit, - onObjectCheckboxClicked: (Id, Boolean) -> Unit + onObjectCheckboxClicked: (Id, Boolean) -> Unit, + onCreateElement: (WidgetView) -> Unit = {} ) { Box( modifier = Modifier @@ -545,7 +553,8 @@ private fun ListOfObjectsItem( onWidgetMenuAction(item.id, action) }, onToggleExpandedWidgetState = onToggleExpandedWidgetState, - onObjectCheckboxClicked = onObjectCheckboxClicked + onObjectCheckboxClicked = onObjectCheckboxClicked, + onCreateElement = onCreateElement ) AnimatedVisibility( visible = mode is InteractionMode.Edit, @@ -584,7 +593,8 @@ private fun SetOfObjectsItem( onChangeWidgetView: (WidgetId, ViewId) -> Unit, onToggleExpandedWidgetState: (WidgetId) -> Unit, onObjectCheckboxClicked: (Id, Boolean) -> Unit, - onCreateDataViewObject: (WidgetId, ViewId?) -> Unit + onCreateDataViewObject: (WidgetId, ViewId?) -> Unit, + onCreateElement: (WidgetView) -> Unit = {} ) { Box( modifier = Modifier @@ -610,7 +620,8 @@ private fun SetOfObjectsItem( onToggleExpandedWidgetState = onToggleExpandedWidgetState, mode = mode, onObjectCheckboxClicked = onObjectCheckboxClicked, - onCreateDataViewObject = onCreateDataViewObject + onCreateDataViewObject = onCreateDataViewObject, + onCreateElement = onCreateElement ) AnimatedVisibility( visible = mode is InteractionMode.Edit, diff --git a/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreenFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreenFragment.kt index 6a0be82b9f..82cd0e412c 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreenFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreenFragment.kt @@ -210,6 +210,7 @@ class HomeScreenFragment : BaseComposeFragment(), onNavBarShareButtonClicked = vm::onNavBarShareIconClicked, navPanelState = vm.navPanelState.collectAsStateWithLifecycle().value, onHomeButtonClicked = vm::onHomeButtonClicked, + onCreateElement = vm::onCreateWidgetElementClicked ) } diff --git a/app/src/main/java/com/anytypeio/anytype/ui/widgets/types/DataViewWidget.kt b/app/src/main/java/com/anytypeio/anytype/ui/widgets/types/DataViewWidget.kt index 392e5939cc..1cd34c6fe5 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/widgets/types/DataViewWidget.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/widgets/types/DataViewWidget.kt @@ -71,7 +71,8 @@ fun DataViewListWidgetCard( onChangeWidgetView: (WidgetId, ViewId) -> Unit, onToggleExpandedWidgetState: (WidgetId) -> Unit, onObjectCheckboxClicked: (Id, Boolean) -> Unit, - onCreateDataViewObject: (WidgetId, ViewId?) -> Unit + onCreateDataViewObject: (WidgetId, ViewId?) -> Unit, + onCreateElement: (WidgetView) -> Unit = {} ) { val isCardMenuExpanded = remember { mutableStateOf(false) @@ -115,7 +116,9 @@ fun DataViewListWidgetCard( isExpanded = item.isExpanded, isInEditMode = mode is InteractionMode.Edit, hasReadOnlyAccess = mode is InteractionMode.ReadOnly, - onDropDownMenuAction = onDropDownMenuAction + onDropDownMenuAction = onDropDownMenuAction, + canCreate = mode is InteractionMode.Default, + onCreateElement = { onCreateElement(item) } ) if (item.tabs.size > 1 && item.isExpanded) { DataViewTabs( diff --git a/app/src/main/java/com/anytypeio/anytype/ui/widgets/types/ListWidget.kt b/app/src/main/java/com/anytypeio/anytype/ui/widgets/types/ListWidget.kt index 5efdef60f0..5a3a57141f 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/widgets/types/ListWidget.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/widgets/types/ListWidget.kt @@ -44,7 +44,8 @@ fun ListWidgetCard( onWidgetSourceClicked: (Widget.Source) -> Unit, onDropDownMenuAction: (DropDownMenuAction) -> Unit, onToggleExpandedWidgetState: (WidgetId) -> Unit, - onObjectCheckboxClicked: (Id, Boolean) -> Unit + onObjectCheckboxClicked: (Id, Boolean) -> Unit, + onCreateElement: (WidgetView) -> Unit = {} ) { val isCardMenuExpanded = remember { mutableStateOf(false) @@ -89,7 +90,9 @@ fun ListWidgetCard( isExpanded = item.isExpanded, isInEditMode = mode is InteractionMode.Edit, hasReadOnlyAccess = mode is InteractionMode.ReadOnly, - onDropDownMenuAction = onDropDownMenuAction + onDropDownMenuAction = onDropDownMenuAction, + canCreate = (item.type is Type.Favorites && mode is InteractionMode.Default), + onCreateElement = { onCreateElement(item) } ) if (item.elements.isNotEmpty()) { if (item.isCompact) { diff --git a/app/src/main/java/com/anytypeio/anytype/ui/widgets/types/TreeWidget.kt b/app/src/main/java/com/anytypeio/anytype/ui/widgets/types/TreeWidget.kt index 530cdc0658..733902856b 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/widgets/types/TreeWidget.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/widgets/types/TreeWidget.kt @@ -10,16 +10,19 @@ import androidx.compose.animation.fadeOut import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Divider import androidx.compose.material.Text @@ -32,6 +35,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.hapticfeedback.HapticFeedbackType @@ -254,9 +258,11 @@ fun WidgetHeader( onWidgetHeaderClicked: () -> Unit, onDropDownMenuAction: (DropDownMenuAction) -> Unit, onExpandElement: () -> Unit = {}, + onCreateElement: () -> Unit = {}, isExpanded: Boolean = false, isInEditMode: Boolean = true, - hasReadOnlyAccess: Boolean = false + hasReadOnlyAccess: Boolean = false, + canCreate: Boolean = false ) { val haptic = LocalHapticFeedback.current Box( @@ -297,6 +303,25 @@ fun WidgetHeader( ) ) + if (canCreate) { + Box( + Modifier + .align(Alignment.CenterEnd) + .padding(end = 42.dp) + .fillMaxHeight() + .width(34.dp) + .noRippleClickable { + onCreateElement() + } + ) { + Image( + painter = painterResource(R.drawable.ic_widget_system_plus_18), + contentDescription = stringResource(R.string.content_description_plus_button), + modifier = Modifier.align(Alignment.Center) + ) + } + } + WidgetArrow( modifier = Modifier.align(Alignment.CenterEnd), isInEditMode = isInEditMode, diff --git a/app/src/main/res/drawable/ic_widget_tree_expand.xml b/app/src/main/res/drawable/ic_widget_tree_expand.xml index efb181ce6c..c45c0e475f 100644 --- a/app/src/main/res/drawable/ic_widget_tree_expand.xml +++ b/app/src/main/res/drawable/ic_widget_tree_expand.xml @@ -7,6 +7,6 @@ android:pathData="M7.5,15L12.5,10L7.5,5" android:strokeWidth="1.5" android:fillColor="@color/transparent" - android:strokeColor="@color/text_primary" + android:strokeColor="@color/glyph_active" android:strokeLineCap="round"/> diff --git a/core-ui/src/main/res/drawable/ic_widget_system_plus_18.xml b/core-ui/src/main/res/drawable/ic_widget_system_plus_18.xml new file mode 100644 index 0000000000..fd677d6ddc --- /dev/null +++ b/core-ui/src/main/res/drawable/ic_widget_system_plus_18.xml @@ -0,0 +1,10 @@ + + + diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/dashboard/interactor/SetObjectListIsFavorite.kt b/domain/src/main/java/com/anytypeio/anytype/domain/dashboard/interactor/SetObjectListIsFavorite.kt index be4b96bb7b..095ce82e14 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/dashboard/interactor/SetObjectListIsFavorite.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/dashboard/interactor/SetObjectListIsFavorite.kt @@ -4,8 +4,9 @@ import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers import com.anytypeio.anytype.domain.base.ResultInteractor import com.anytypeio.anytype.domain.block.repo.BlockRepository +import javax.inject.Inject -class SetObjectListIsFavorite( +class SetObjectListIsFavorite @Inject constructor( private val repo: BlockRepository, dispatchers: AppCoroutineDispatchers ) : ResultInteractor(dispatchers.io) { diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModel.kt index 171998da07..a2da830119 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModel.kt @@ -16,6 +16,7 @@ import com.anytypeio.anytype.core_models.DVFilterCondition import com.anytypeio.anytype.core_models.Event import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.ObjectType +import com.anytypeio.anytype.core_models.ObjectTypeIds import com.anytypeio.anytype.core_models.ObjectTypeUniqueKeys import com.anytypeio.anytype.core_models.ObjectView import com.anytypeio.anytype.core_models.ObjectWrapper @@ -47,6 +48,7 @@ import com.anytypeio.anytype.domain.bin.EmptyBin import com.anytypeio.anytype.domain.block.interactor.CreateBlock import com.anytypeio.anytype.domain.block.interactor.Move import com.anytypeio.anytype.domain.collections.AddObjectToCollection +import com.anytypeio.anytype.domain.dashboard.interactor.SetObjectListIsFavorite import com.anytypeio.anytype.domain.dataview.interactor.CreateDataViewObject import com.anytypeio.anytype.domain.event.interactor.InterceptEvents import com.anytypeio.anytype.domain.launch.GetDefaultObjectType @@ -226,7 +228,8 @@ class HomeScreenViewModel( private val spaceViewSubscriptionContainer: SpaceViewSubscriptionContainer, private val getSpaceInviteLink: GetSpaceInviteLink, private val deleteSpace: DeleteSpace, - private val spaceMembers: ActiveSpaceMemberSubscriptionContainer + private val spaceMembers: ActiveSpaceMemberSubscriptionContainer, + private val setAsFavourite: SetObjectListIsFavorite ) : NavigationViewModel(), Reducer, WidgetActiveViewStateHolder by widgetActiveViewStateHolder, @@ -2347,6 +2350,66 @@ class HomeScreenViewModel( } } + fun onCreateWidgetElementClicked(view: WidgetView) { + viewModelScope.launch { + + } + when(view) { + is WidgetView.ListOfObjects -> { + if (view.type == WidgetView.ListOfObjects.Type.Favorites) { + viewModelScope.launch { + val space = SpaceId(spaceManager.get()) + val type = getDefaultObjectType.async(space) + .getOrNull() + ?.type ?: TypeKey(ObjectTypeIds.PAGE) + createObject.async( + params = CreateObject.Param( + space = SpaceId(spaceManager.get()), + type = type, + prefilled = mapOf(Relations.IS_FAVORITE to true) + ) + ).onSuccess { result -> + proceedWithNavigation(result.obj.navigation()) + setAsFavourite.async( + params = SetObjectListIsFavorite.Params( + objectIds = listOf(result.obj.id), + isFavorite = true + ) + ) + }.onFailure { + Timber.e(it, "Error while creating object") + } + } + } + } + is WidgetView.SetOfObjects -> { + viewModelScope.launch { + val source = view.source + if (source is Widget.Source.Default) { + if (source.obj.layout == ObjectType.Layout.OBJECT_TYPE) { + val wrapper = ObjectWrapper.Type(source.obj.map) + createObject.async( + params = CreateObject.Param( + space = SpaceId(spaceManager.get()), + type = TypeKey(wrapper.uniqueKey), + prefilled = mapOf(Relations.IS_FAVORITE to true) + ) + ).onSuccess { result -> + proceedWithNavigation(result.obj.navigation()) + } + } else { + Timber.w("Unexpected source layout: ${source.obj.layout}") + } + } + } + + } + else -> { + Timber.w("Unexpected widget type: ${view::class.java.simpleName}") + } + } + } + sealed class Navigation { data class OpenObject(val ctx: Id, val space: Id) : Navigation() data class OpenChat(val ctx: Id, val space: Id) : Navigation() @@ -2426,7 +2489,8 @@ class HomeScreenViewModel( private val spaceViewSubscriptionContainer: SpaceViewSubscriptionContainer, private val getSpaceInviteLink: GetSpaceInviteLink, private val deleteSpace: DeleteSpace, - private val activeSpaceMemberSubscriptionContainer: ActiveSpaceMemberSubscriptionContainer + private val activeSpaceMemberSubscriptionContainer: ActiveSpaceMemberSubscriptionContainer, + private val setObjectListIsFavorite: SetObjectListIsFavorite ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T = HomeScreenViewModel( @@ -2483,7 +2547,8 @@ class HomeScreenViewModel( spaceViewSubscriptionContainer = spaceViewSubscriptionContainer, getSpaceInviteLink = getSpaceInviteLink, deleteSpace = this@Factory.deleteSpace, - spaceMembers = activeSpaceMemberSubscriptionContainer + spaceMembers = activeSpaceMemberSubscriptionContainer, + setAsFavourite = setObjectListIsFavorite ) as T } diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModelTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModelTest.kt index aa0a0e0fc7..214cd21ab6 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModelTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModelTest.kt @@ -40,6 +40,7 @@ import com.anytypeio.anytype.domain.block.interactor.Move import com.anytypeio.anytype.domain.collections.AddObjectToCollection import com.anytypeio.anytype.domain.config.ConfigStorage import com.anytypeio.anytype.domain.config.Gateway +import com.anytypeio.anytype.domain.dashboard.interactor.SetObjectListIsFavorite import com.anytypeio.anytype.domain.dataview.interactor.CreateDataViewObject import com.anytypeio.anytype.domain.debugging.Logger import com.anytypeio.anytype.domain.event.interactor.InterceptEvents @@ -280,6 +281,9 @@ class HomeScreenViewModelTest { @Mock lateinit var deleteSpace: DeleteSpace + @Mock + lateinit var setObjectListIsFavorite: SetObjectListIsFavorite + lateinit var userPermissionProvider: UserPermissionProvider private val objectPayloadDispatcher = Dispatcher.Default() @@ -2925,7 +2929,8 @@ class HomeScreenViewModelTest { getSpaceInviteLink = getSpaceInviteLink, spaceMembers = activeSpaceMemberSubscriptionContainer, spaceViewSubscriptionContainer = spaceViewSubscriptionContainer, - deleteSpace = deleteSpace + deleteSpace = deleteSpace, + setAsFavourite = setObjectListIsFavorite ) companion object {