From c0ee5ef7eb6f23ada8b1b662aafdf16d8ea5e2d1 Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Fri, 15 Mar 2024 00:36:59 +0100 Subject: [PATCH] DROID-2319 Multiplayer | Enhancement | Space settings behavior for read-only spaces (#1007) --- .../di/feature/spaces/SpaceSettingsDI.kt | 2 + .../anytypeio/anytype/ui/home/HomeScreen.kt | 33 +++++---- .../spaces/SpaceSettingsViewModel.kt | 68 ++----------------- .../ui_settings/main/MainSettingScreen.kt | 6 +- .../anytype/ui_settings/main/Views.kt | 10 +-- .../anytype/ui_settings/space/Settings.kt | 8 ++- 6 files changed, 46 insertions(+), 81 deletions(-) diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/spaces/SpaceSettingsDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/spaces/SpaceSettingsDI.kt index e4c7112695..6301048580 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/spaces/SpaceSettingsDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/spaces/SpaceSettingsDI.kt @@ -15,6 +15,7 @@ import com.anytypeio.anytype.domain.debugging.DebugSpaceContentSaver import com.anytypeio.anytype.domain.debugging.DebugSpaceShareDownloader import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer import com.anytypeio.anytype.domain.misc.UrlBuilder +import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider import com.anytypeio.anytype.domain.workspace.SpaceManager import com.anytypeio.anytype.presentation.spaces.SpaceGradientProvider import com.anytypeio.anytype.presentation.spaces.SpaceSettingsViewModel @@ -97,4 +98,5 @@ interface SpaceSettingsDependencies : ComponentDependencies { fun container(): StorelessSubscriptionContainer fun config(): ConfigStorage fun context(): Context + fun userPermission(): UserPermissionProvider } \ No newline at end of file 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 9f38eba75f..f8ee118018 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 @@ -152,7 +152,7 @@ fun HomeScreen( } } AnimatedVisibility( - visible = mode is InteractionMode.Default, + visible = mode !is InteractionMode.Edit, modifier = Modifier .align(Alignment.BottomCenter) .padding(bottom = 20.dp), @@ -165,7 +165,8 @@ fun HomeScreen( onCreateNewObjectClicked = throttledClick(onCreateNewObjectClicked), onProfileClicked = throttledClick(onProfileClicked), onCreateNewObjectLongClicked = onCreateNewObjectLongClicked, - modifier = Modifier + modifier = Modifier, + isReadOnlyAccess = mode is InteractionMode.ReadOnly ) } } @@ -526,7 +527,6 @@ fun HomeScreenButton( } } -@OptIn(ExperimentalFoundationApi::class) @Composable fun HomeScreenBottomToolbar( profileIcon: ProfileIconView, @@ -534,7 +534,8 @@ fun HomeScreenBottomToolbar( onSearchClicked: () -> Unit, onCreateNewObjectClicked: () -> Unit, onCreateNewObjectLongClicked: () -> Unit, - onProfileClicked: () -> Unit + onProfileClicked: () -> Unit, + isReadOnlyAccess: Boolean ) { val haptic = LocalHapticFeedback.current Row( @@ -561,16 +562,22 @@ fun HomeScreenBottomToolbar( Box( modifier = Modifier .weight(1f) + .alpha(if (isReadOnlyAccess) 0.2f else 1f) .fillMaxSize() - .noRippleCombinedClickable( - onLongClicked = { - onCreateNewObjectLongClicked().also { - haptic.performHapticFeedback(HapticFeedbackType.LongPress) - } - }, - onClick = { - onCreateNewObjectClicked() - } + .then( + if (isReadOnlyAccess) + Modifier.noRippleCombinedClickable( + onLongClicked = { + onCreateNewObjectLongClicked().also { + haptic.performHapticFeedback(HapticFeedbackType.LongPress) + } + }, + onClick = { + onCreateNewObjectClicked() + } + ) + else + Modifier ) ) { Image( diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/spaces/SpaceSettingsViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/spaces/SpaceSettingsViewModel.kt index 0be5a301e4..82a142a252 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/spaces/SpaceSettingsViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/spaces/SpaceSettingsViewModel.kt @@ -8,7 +8,6 @@ import com.anytypeio.anytype.analytics.base.EventsDictionary import com.anytypeio.anytype.analytics.base.EventsPropertiesKey import com.anytypeio.anytype.analytics.base.sendEvent import com.anytypeio.anytype.analytics.props.Props -import com.anytypeio.anytype.core_models.Account import com.anytypeio.anytype.core_models.DVFilter import com.anytypeio.anytype.core_models.DVFilterCondition import com.anytypeio.anytype.core_models.Filepath @@ -23,23 +22,21 @@ import com.anytypeio.anytype.core_models.multiplayer.SpaceAccessType import com.anytypeio.anytype.core_models.multiplayer.SpaceMemberPermissions import com.anytypeio.anytype.core_models.primitives.SpaceId import com.anytypeio.anytype.core_utils.ui.ViewState -import com.anytypeio.anytype.domain.auth.interactor.GetAccount import com.anytypeio.anytype.domain.base.fold import com.anytypeio.anytype.domain.config.ConfigStorage import com.anytypeio.anytype.domain.debugging.DebugSpaceShareDownloader import com.anytypeio.anytype.domain.library.StoreSearchParams import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer import com.anytypeio.anytype.domain.misc.UrlBuilder +import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider import com.anytypeio.anytype.domain.spaces.DeleteSpace import com.anytypeio.anytype.domain.spaces.SetSpaceDetails import com.anytypeio.anytype.domain.workspace.SpaceManager import com.anytypeio.anytype.presentation.common.BaseViewModel -import com.anytypeio.anytype.presentation.search.ObjectSearchConstants import javax.inject.Inject import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.launch import timber.log.Timber @@ -56,7 +53,7 @@ class SpaceSettingsViewModel( private val configStorage: ConfigStorage, private val debugSpaceShareDownloader: DebugSpaceShareDownloader, private val spaceGradientProvider: SpaceGradientProvider, - private val getAccount: GetAccount + private val userPermissionProvider: UserPermissionProvider ): BaseViewModel() { val commands = MutableSharedFlow() @@ -73,7 +70,6 @@ class SpaceSettingsViewModel( eventName = EventsDictionary.screenSettingSpacesSpaceIndex ) } - proceedWithUserAsSpaceMemberPermissions() proceedWithFetchingSpaceMetaData() } @@ -117,7 +113,7 @@ class SpaceSettingsViewModel( ) ).mapNotNull { results -> results.firstOrNull() - }.combine(permissions) { wrapper, permission -> + }.combine(userPermissionProvider.observe(params.space)) { wrapper, permission -> val spaceView = ObjectWrapper.SpaceView(wrapper.map) SpaceData( name = wrapper.name.orEmpty(), @@ -135,7 +131,7 @@ class SpaceSettingsViewModel( network = config?.network.orEmpty(), isDeletable = resolveIsSpaceDeletable(spaceView), spaceType = spaceView.spaceAccessType?.asSpaceType() ?: UNKNOWN_SPACE_TYPE, - permissions = permission + permissions = permission ?: SpaceMemberPermissions.NO_PERMISSIONS ) }.collect { spaceData -> spaceViewState.value = ViewState.Success(spaceData) @@ -146,55 +142,6 @@ class SpaceSettingsViewModel( private fun resolveIsSpaceDeletable(spaceView: ObjectWrapper.SpaceView) = spaceView.spaceAccessType != null && spaceView.spaceAccessType != SpaceAccessType.PERSONAL - private fun proceedWithUserAsSpaceMemberPermissions() { - viewModelScope.launch { - getAccount.async(Unit).fold( - onSuccess = { account -> - proceedWithSpaceMemberPermissionSubscription(account) - }, - onFailure = { - Timber.e(it, "Could not get account") - } - ) - } - } - - private suspend fun proceedWithSpaceMemberPermissionSubscription(account: Account) { - storelessSubscriptionContainer.subscribe( - searchParams = StoreSearchParams( - subscription = SPACE_SETTINGS_PARTICIPANT_SUBSCRIPTION, - filters = buildList { - addAll( - ObjectSearchConstants.filterParticipants( - spaces = listOf(params.space.id) - ) - ) - add( - DVFilter( - relation = Relations.IDENTITY, - value = account.id, - condition = DVFilterCondition.EQUAL - ) - ) - }, - sorts = emptyList(), - keys = ObjectSearchConstants.spaceMemberKeys, - limit = 1 - ) - ).map { results -> - if (results.isNotEmpty()) - ObjectWrapper.SpaceMember(results.first().map) - else - null - }.collect { user -> - if (user != null) - permissions.value = - user.permissions ?: SpaceMemberPermissions.NO_PERMISSIONS - else - Timber.w("User-as-space-member permission is not found.") - } - } - fun onNameSet(name: String) { Timber.d("onNameSet") if (name.isEmpty()) return @@ -366,7 +313,6 @@ class SpaceSettingsViewModel( class Factory @Inject constructor( private val params: Params, - private val getAccount: GetAccount, private val analytics: Analytics, private val storelessSubscriptionContainer: StorelessSubscriptionContainer, private val urlBuilder: UrlBuilder, @@ -376,7 +322,8 @@ class SpaceSettingsViewModel( private val deleteSpace: DeleteSpace, private val configStorage: ConfigStorage, private val debugFileShareDownloader: DebugSpaceShareDownloader, - private val spaceGradientProvider: SpaceGradientProvider + private val spaceGradientProvider: SpaceGradientProvider, + private val userPermissionProvider: UserPermissionProvider ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create( @@ -393,7 +340,7 @@ class SpaceSettingsViewModel( debugSpaceShareDownloader = debugFileShareDownloader, spaceGradientProvider = spaceGradientProvider, params = params, - getAccount = getAccount + userPermissionProvider = userPermissionProvider ) as T } @@ -402,6 +349,5 @@ class SpaceSettingsViewModel( companion object { const val SPACE_DEBUG_MSG = "Kindly share this debug logs with Anytype developers." const val SPACE_SETTINGS_SUBSCRIPTION = "subscription.space-settings.space-views" - const val SPACE_SETTINGS_PARTICIPANT_SUBSCRIPTION = "subscription.space-settings.participant" } } \ No newline at end of file diff --git a/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/main/MainSettingScreen.kt b/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/main/MainSettingScreen.kt index 10589c8b7c..3203416488 100644 --- a/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/main/MainSettingScreen.kt +++ b/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/main/MainSettingScreen.kt @@ -32,7 +32,8 @@ fun SpaceHeader( icon: SpaceIconView?, modifier: Modifier = Modifier, onNameSet: (String) -> Unit, - onRandomGradientClicked: () -> Unit + onRandomGradientClicked: () -> Unit, + isEditEnabled: Boolean ) { val isSpaceIconMenuExpanded = remember { mutableStateOf(false) @@ -78,7 +79,8 @@ fun SpaceHeader( SpaceNameBlock( modifier = Modifier, name = name, - onNameSet = onNameSet + onNameSet = onNameSet, + isEditEnabled = isEditEnabled ) } } diff --git a/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/main/Views.kt b/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/main/Views.kt index 7fbd2d2c9c..7fc972dc82 100644 --- a/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/main/Views.kt +++ b/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/main/Views.kt @@ -67,7 +67,8 @@ fun Section(modifier: Modifier = Modifier, title: String) { fun SpaceNameBlock( modifier: Modifier = Modifier, name: String, - onNameSet: (String) -> Unit + onNameSet: (String) -> Unit, + isEditEnabled: Boolean ) { val nameValue = remember { mutableStateOf(name) } @@ -99,6 +100,7 @@ fun SpaceNameBlock( focusManager.clearFocus() } ), + isEditEnabled = isEditEnabled ) } } @@ -199,22 +201,22 @@ fun SpaceImageBlock( } } +@OptIn(ExperimentalMaterialApi::class) @Composable fun SettingsTextField( value: String, onValueChange: (String) -> Unit, visualTransformation: VisualTransformation = VisualTransformation.None, keyboardActions: KeyboardActions = KeyboardActions.Default, + isEditEnabled: Boolean ) { - - @OptIn(ExperimentalMaterialApi::class) BasicTextField( value = value, modifier = Modifier .padding(top = 4.dp, end = 20.dp) .fillMaxWidth(), onValueChange = onValueChange, - enabled = true, + enabled = isEditEnabled, readOnly = false, textStyle = HeadlineHeading.copy(color = colorResource(id = R.color.text_primary)), cursorBrush = SolidColor(colorResource(id = R.color.orange)), diff --git a/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/space/Settings.kt b/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/space/Settings.kt index 933807d22a..ff60044833 100644 --- a/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/space/Settings.kt +++ b/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/space/Settings.kt @@ -82,7 +82,13 @@ fun SpaceSettingsScreen( else -> null }, onNameSet = onNameSet, - onRandomGradientClicked = onRandomGradientClicked + onRandomGradientClicked = onRandomGradientClicked, + isEditEnabled = when(spaceData) { + is ViewState.Error -> false + ViewState.Init -> false + ViewState.Loading -> false + is ViewState.Success -> spaceData.data.permissions.isOwnerOrEditor() + } ) } item { Divider() }