diff --git a/app/src/main/java/com/anytypeio/anytype/app/DefaultFeatureToggles.kt b/app/src/main/java/com/anytypeio/anytype/app/DefaultFeatureToggles.kt index 7aa3f129cc..4cc444dab3 100644 --- a/app/src/main/java/com/anytypeio/anytype/app/DefaultFeatureToggles.kt +++ b/app/src/main/java/com/anytypeio/anytype/app/DefaultFeatureToggles.kt @@ -16,8 +16,7 @@ class DefaultFeatureToggles @Inject constructor( override val isLogFromGoProcess = BuildConfig.LOG_FROM_MW_LIBRARY && buildProvider.isDebug() - override val isLogMiddlewareInteraction = - BuildConfig.LOG_MW_INTERACTION && buildProvider.isDebug() + override val isLogMiddlewareInteraction = BuildConfig.LOG_MW_INTERACTION && buildProvider.isDebug() override val excludeThreadStatusLogging: Boolean = true diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/Notification.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/Notification.kt index ecbac08944..3590bf343c 100644 --- a/core-models/src/main/java/com/anytypeio/anytype/core_models/Notification.kt +++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/Notification.kt @@ -27,7 +27,7 @@ data class Notification( sealed class NotificationPayload { data class GalleryImport( - val processId: String, + val processId: Id, val errorCode: ImportErrorCode, val spaceId: SpaceId, val name: String @@ -35,6 +35,15 @@ sealed class NotificationPayload { data class RequestToJoin( val spaceId: SpaceId, + val spaceName: String, + val identity: Id, + val identityName: String, + val identityIcon: String + ) : NotificationPayload() + + data class RequestToLeave( + val spaceId: SpaceId, + val spaceName: String, val identity: String, val identityName: String, val identityIcon: String @@ -42,29 +51,27 @@ sealed class NotificationPayload { data class ParticipantRequestApproved( val spaceId: SpaceId, + val spaceName: String, val permissions: SpaceMemberPermissions ) : NotificationPayload() - data class RequestToLeave( + + data class ParticipantRemove( val spaceId: SpaceId, + val spaceName: String, val identity: String, val identityName: String, val identityIcon: String ) : NotificationPayload() - data class ParticipantRemove( - val identity: String, - val identityName: String, - val identityIcon: String, - val spaceId: SpaceId - ) : NotificationPayload() - data class ParticipantRequestDecline( - val spaceId: SpaceId + val spaceId: SpaceId, + val spaceName: String ) : NotificationPayload() data class ParticipantPermissionsChange( val spaceId: SpaceId, + val spaceName: String, val permissions: SpaceMemberPermissions ) : NotificationPayload() diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/notifications/MemberNotifications.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/notifications/MemberNotifications.kt new file mode 100644 index 0000000000..8f71819200 --- /dev/null +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/notifications/MemberNotifications.kt @@ -0,0 +1,80 @@ +package com.anytypeio.anytype.core_ui.notifications + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.anytypeio.anytype.core_ui.R +import com.anytypeio.anytype.core_ui.views.Caption1Regular + +@Composable +fun MemberRequestApprovedNotification( + spaceName: String, + isReadOnly: Boolean +) { + Box( + modifier = Modifier + .fillMaxWidth() + .background( + color = colorResource(id = R.color.background_primary), + ) + .padding(horizontal = 20.dp, vertical = 16.dp) + ) { + val placeholder = stringResource(id = R.string.untitled) + val msg = if (isReadOnly) + stringResource( + id = R.string.multiplayer_notification_member_request_approved_with_read_only_rights, + spaceName.ifEmpty { placeholder } + ) + else + stringResource( + id = R.string.multiplayer_notification_member_request_approved_with_read_only_rights, + spaceName.ifEmpty { placeholder } + ) + Text( + text = msg, + modifier = Modifier + .align(Alignment.CenterStart), + color = colorResource(id = R.color.text_secondary), + style = Caption1Regular, + overflow = TextOverflow.Ellipsis + ) + } +} + +@Composable +fun MemberSpaceRemovedNotification() { + Box( + modifier = Modifier + .fillMaxWidth() + .background(color = colorResource(id = R.color.background_primary),) + .padding(horizontal = 20.dp, vertical = 16.dp) + ) { + Text( + text = stringResource(id = R.string.multiplayer_notification_member_removed_from_space), + modifier = Modifier + .align(Alignment.CenterStart), + color = colorResource(id = R.color.text_secondary), + style = Caption1Regular, + overflow = TextOverflow.Ellipsis + ) + } +} + +@Composable +@Preview +fun MemberRequestApprovedWithAccessRightsNotificationPreview() { + MemberRequestApprovedNotification( + spaceName = "Art historians", + isReadOnly = true + ) +} \ No newline at end of file diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/notifications/NotificationWidgets.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/notifications/NotificationWidgets.kt index 982af3648a..2cc18c5e70 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/notifications/NotificationWidgets.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/notifications/NotificationWidgets.kt @@ -1,32 +1,46 @@ package com.anytypeio.anytype.core_ui.notifications import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.anytypeio.anytype.core_models.ImportErrorCode -import com.anytypeio.anytype.core_models.ImportErrorCode.* +import com.anytypeio.anytype.core_models.ImportErrorCode.BAD_INPUT +import com.anytypeio.anytype.core_models.ImportErrorCode.FILE_LOAD_ERROR +import com.anytypeio.anytype.core_models.ImportErrorCode.IMPORT_IS_CANCELED +import com.anytypeio.anytype.core_models.ImportErrorCode.INSUFFICIENT_PERMISSIONS +import com.anytypeio.anytype.core_models.ImportErrorCode.INTERNAL_ERROR +import com.anytypeio.anytype.core_models.ImportErrorCode.LIMIT_OF_ROWS_OR_RELATIONS_EXCEEDED +import com.anytypeio.anytype.core_models.ImportErrorCode.NO_OBJECTS_TO_IMPORT +import com.anytypeio.anytype.core_models.ImportErrorCode.NULL +import com.anytypeio.anytype.core_models.ImportErrorCode.UNKNOWN_ERROR import com.anytypeio.anytype.core_models.primitives.SpaceId import com.anytypeio.anytype.core_ui.R import com.anytypeio.anytype.core_ui.foundation.AlertConfig import com.anytypeio.anytype.core_ui.foundation.AlertIcon import com.anytypeio.anytype.core_ui.foundation.GRADIENT_TYPE_GREEN import com.anytypeio.anytype.core_ui.foundation.GRADIENT_TYPE_RED +import com.anytypeio.anytype.core_ui.foundation.noRippleClickable import com.anytypeio.anytype.core_ui.views.BodyCalloutRegular import com.anytypeio.anytype.core_ui.views.ButtonPrimary import com.anytypeio.anytype.core_ui.views.ButtonSecondary import com.anytypeio.anytype.core_ui.views.ButtonSize +import com.anytypeio.anytype.core_ui.views.Caption1Regular import com.anytypeio.anytype.core_ui.views.HeadlineSubheading import com.anytypeio.anytype.presentation.notifications.NotificationsScreenState @@ -66,7 +80,29 @@ fun NotificationsScreen( onButtonClick = onErrorButtonClick ) } - + is NotificationsScreenState.Multiplayer.RequestToJoin -> { + OwnerUserRequestToJoin( + name = state.identityName, + spaceName = state.spaceName, + onManageClicked = {} + ) + } + is NotificationsScreenState.Multiplayer.RequestToLeave -> { + OwnerUserRequestToLeave( + name = state.name, + spaceName = state.spaceName, + onManageClicked = {} + ) + } + is NotificationsScreenState.Multiplayer.MemberRequestApproved -> { + MemberRequestApprovedNotification( + spaceName = state.spaceName, + isReadOnly = state.isReadOnly + ) + } + is NotificationsScreenState.Multiplayer.MemberSpaceRemove -> { + MemberSpaceRemovedNotification() + } NotificationsScreenState.Hidden -> {} } } @@ -204,4 +240,64 @@ fun NotificationsScreenPreviewError() { onActionButtonClick = {}, onErrorButtonClick = {} ) +} + +@Composable +fun UserJoinedSpaceWithAccessRightsNotification( + name: String, + spaceName: String, + onManageClicked: () -> Unit, + isReadOnly: Boolean +) { + Box( + modifier = Modifier + .fillMaxWidth() + .background( + color = colorResource(id = R.color.background_notification_primary), + shape = RoundedCornerShape(8.dp) + ) + .padding(horizontal = 20.dp, vertical = 16.dp) + .noRippleClickable { onManageClicked() } + ) { + Box( + modifier = Modifier + .size(32.dp) + .background( + color = Color.Red, + shape = CircleShape + ) + ) + val placeholder = stringResource(id = R.string.untitled) + val msg = if (isReadOnly) + stringResource( + id = R.string.multiplayer_notification_member_joined_space_with_read_only_rights, + name.ifEmpty { placeholder }, + spaceName.ifEmpty { placeholder } + ) + else + stringResource( + id = R.string.multiplayer_notification_member_joined_space_with_read_only_rights, + name.ifEmpty { placeholder }, + spaceName.ifEmpty { placeholder } + ) + Text( + text = msg, + modifier = Modifier + .align(Alignment.CenterStart) + .padding(start = 44.dp), + color = colorResource(id = R.color.text_secondary), + style = Caption1Regular + ) + } +} + +@Composable +@Preview +private fun UserJoinedSpaceWithAccessRightsNotificationPreview() { + UserJoinedSpaceWithAccessRightsNotification( + name = "Carl Einstein", + spaceName = "Art historians", + isReadOnly = true, + onManageClicked = {} + ) } \ No newline at end of file diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/notifications/OwnerNotifications.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/notifications/OwnerNotifications.kt new file mode 100644 index 0000000000..255e2f6762 --- /dev/null +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/notifications/OwnerNotifications.kt @@ -0,0 +1,136 @@ +package com.anytypeio.anytype.core_ui.notifications + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.anytypeio.anytype.core_ui.R +import com.anytypeio.anytype.core_ui.foundation.noRippleClickable +import com.anytypeio.anytype.core_ui.views.Caption1Medium +import com.anytypeio.anytype.core_ui.views.Caption1Regular + +@Composable +fun OwnerUserRequestToJoin( + name: String, + spaceName: String, + onManageClicked: () -> Unit +) { + Row( + modifier = Modifier + .fillMaxWidth() + .background( + color = colorResource(id = R.color.background_primary) + ) + .padding(horizontal = 20.dp, vertical = 16.dp) + .noRippleClickable { onManageClicked() }, + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier + .size(32.dp) + .background( + color = Color.Red, + shape = CircleShape + ) + ) + val placeholder = stringResource(id = R.string.untitled) + Text( + text = stringResource( + id = R.string.multiplayer_notification_member_user_sends_join_request, + name.ifEmpty { placeholder }, + spaceName.ifEmpty { placeholder } + ), + modifier = Modifier + .padding(start = 12.dp) + .weight(1f), + color = colorResource(id = R.color.text_secondary), + style = Caption1Regular + ) + Text( + text = stringResource(id = R.string.multiplayer_notification_view_request), + modifier = Modifier + .padding(start = 12.dp), + color = colorResource(id = R.color.text_primary), + style = Caption1Medium + ) + } +} + +@Composable +fun OwnerUserRequestToLeave( + name: String, + spaceName: String, + onManageClicked: () -> Unit +) { + Row( + modifier = Modifier + .fillMaxWidth() + .background( + color = colorResource(id = R.color.background_primary) + ) + .padding(horizontal = 20.dp, vertical = 16.dp) + .noRippleClickable { onManageClicked() }, + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier + .size(32.dp) + .background( + color = Color.Red, + shape = CircleShape + ) + ) + val placeholder = stringResource(id = R.string.untitled) + Text( + text = stringResource( + id = R.string.multiplayer_notification_member_user_sends_leave_request, + name.ifEmpty { placeholder }, + spaceName.ifEmpty { placeholder } + ), + modifier = Modifier + .padding(start = 12.dp) + .weight(1f), + color = colorResource(id = R.color.text_secondary), + style = Caption1Regular + ) + Text( + text = stringResource(id = R.string.multiplayer_notification_view_request), + modifier = Modifier + .padding(start = 12.dp), + color = colorResource(id = R.color.text_primary), + style = Caption1Medium + ) + } +} + +@Composable +@Preview +private fun OwnerUserRequestToJoinPreview() { + OwnerUserRequestToJoin( + name = "Carl Einstein", + spaceName = "Art historians", + onManageClicked = {} + ) +} + +@Composable +@Preview +private fun OwnerUserRequestToLeavePreview() { + OwnerUserRequestToLeave( + name = "Aby Warburg", + spaceName = "Art historians", + onManageClicked = {} + ) +} \ No newline at end of file diff --git a/core-ui/src/main/res/values-night/colors.xml b/core-ui/src/main/res/values-night/colors.xml index 4f0e4cbf6a..906d1a606d 100644 --- a/core-ui/src/main/res/values-night/colors.xml +++ b/core-ui/src/main/res/values-night/colors.xml @@ -10,6 +10,7 @@ #000000 #000000 + #FFFFFF #1F1E1D #24DAD7CA diff --git a/core-ui/src/main/res/values/colors.xml b/core-ui/src/main/res/values/colors.xml index 6719598ea8..5002cbddc2 100644 --- a/core-ui/src/main/res/values/colors.xml +++ b/core-ui/src/main/res/values/colors.xml @@ -117,6 +117,7 @@ #FFFFFF #FFFFFF + #000000 #FFFFFF #144F4F4F diff --git a/localization/src/main/res/values/strings.xml b/localization/src/main/res/values/strings.xml index 3b0c46f391..76bcfb6b05 100644 --- a/localization/src/main/res/values/strings.xml +++ b/localization/src/main/res/values/strings.xml @@ -1354,6 +1354,15 @@ Space deleted Space not found Read-only access. Contact space owner for changes. + %1$s requested to join the %2$s space. + %1$s requested to leave the %2$s space. + New join request + %1$s joined %2$s space with read-only access rights + %1$s joined %2$s space with edit access rights + Your request to join the %1$s space has been approved with read-only access rights. The space will be available on your device soon. + Your request to join the %1$s space has been approved with edit access rights. The space will be available on your device soon. + You have been removed from the space, or the space was deleted by the owner. + View diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/NotificationsMiddlewareChannel.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/NotificationsMiddlewareChannel.kt index b78a4c6951..6eba3c3921 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/NotificationsMiddlewareChannel.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/NotificationsMiddlewareChannel.kt @@ -5,6 +5,7 @@ import com.anytypeio.anytype.data.auth.event.NotificationsRemoteChannel import com.anytypeio.anytype.middleware.EventProxy import com.anytypeio.anytype.middleware.mappers.toCoreModel import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.mapNotNull class NotificationsMiddlewareChannel( @@ -33,6 +34,6 @@ class NotificationsMiddlewareChannel( else -> null } } - } + }.filter { events -> events.isNotEmpty() } } } \ No newline at end of file diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToCoreModelMappers.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToCoreModelMappers.kt index ca7ba81f51..aea539757e 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToCoreModelMappers.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToCoreModelMappers.kt @@ -23,7 +23,6 @@ import com.anytypeio.anytype.core_models.DVViewerCardSize import com.anytypeio.anytype.core_models.DVViewerRelation import com.anytypeio.anytype.core_models.DVViewerType import com.anytypeio.anytype.core_models.Event -import com.anytypeio.anytype.core_models.Process import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.ImportErrorCode import com.anytypeio.anytype.core_models.ManifestInfo @@ -38,6 +37,7 @@ import com.anytypeio.anytype.core_models.ObjectType import com.anytypeio.anytype.core_models.ObjectTypeIds import com.anytypeio.anytype.core_models.ObjectView import com.anytypeio.anytype.core_models.Payload +import com.anytypeio.anytype.core_models.Process import com.anytypeio.anytype.core_models.Relation import com.anytypeio.anytype.core_models.RelationFormat import com.anytypeio.anytype.core_models.RelationLink @@ -909,12 +909,14 @@ fun MNotification.toCoreModel(): Notification { participantPermissionsChange != null -> { NotificationPayload.ParticipantPermissionsChange( spaceId = SpaceId(participantPermissionsChange!!.spaceId), + spaceName = participantPermissionsChange!!.spaceName, permissions = participantPermissionsChange!!.permissions.toCore() ) } requestToJoin != null -> { NotificationPayload.RequestToJoin( spaceId = SpaceId(requestToJoin!!.spaceId), + spaceName = requestToJoin!!.spaceName, identity = requestToJoin!!.identity, identityName = requestToJoin!!.identityName, identityIcon = requestToJoin!!.identityIcon @@ -923,6 +925,7 @@ fun MNotification.toCoreModel(): Notification { requestToLeave != null -> { NotificationPayload.RequestToLeave( spaceId = SpaceId(requestToLeave!!.spaceId), + spaceName = requestToLeave!!.spaceName, identity = requestToLeave!!.identity, identityName = requestToLeave!!.identityName, identityIcon = requestToLeave!!.identityIcon @@ -931,7 +934,8 @@ fun MNotification.toCoreModel(): Notification { participantRequestApproved != null -> { NotificationPayload.ParticipantRequestApproved( spaceId = SpaceId(participantRequestApproved!!.spaceId), - permissions = participantRequestApproved!!.permissions.toCore() + permissions = participantRequestApproved!!.permissions.toCore(), + spaceName = participantRequestApproved!!.spaceName ) } participantRemove != null -> { @@ -939,12 +943,14 @@ fun MNotification.toCoreModel(): Notification { identity = participantRemove!!.identity, identityName = participantRemove!!.identityName, identityIcon = participantRemove!!.identityIcon, - spaceId = SpaceId(participantRemove!!.spaceId) + spaceId = SpaceId(participantRemove!!.spaceId), + spaceName = participantRemove!!.spaceName ) } participantRequestDecline != null -> { NotificationPayload.ParticipantRequestDecline( - spaceId = SpaceId(participantRequestDecline!!.spaceId) + spaceId = SpaceId(participantRequestDecline!!.spaceId), + spaceName = participantRequestDecline!!.spaceName ) } galleryImport != null -> { diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/main/MainViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/main/MainViewModel.kt index 3a4fefe7e0..d318c300d9 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/main/MainViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/main/MainViewModel.kt @@ -96,13 +96,31 @@ class MainViewModel( } private suspend fun handleNotification(event: Notification.Event) { - val payload = event.notification?.payload - if (payload is NotificationPayload.GalleryImport) { - delay(DELAY_BEFORE_SHOWING_NOTIFICATION_SCREEN) - commands.emit(Command.Notifications) - } else { - viewModelScope.launch { - toasts.emit(payload.toString()) + when (val payload = event.notification?.payload) { + is NotificationPayload.GalleryImport -> { + delay(DELAY_BEFORE_SHOWING_NOTIFICATION_SCREEN) + commands.emit(Command.Notifications) + } + is NotificationPayload.RequestToJoin -> { + delay(DELAY_BEFORE_SHOWING_NOTIFICATION_SCREEN) + commands.emit(Command.Notifications) + } + is NotificationPayload.RequestToLeave -> { + delay(DELAY_BEFORE_SHOWING_NOTIFICATION_SCREEN) + commands.emit(Command.Notifications) + } + is NotificationPayload.ParticipantRequestApproved -> { + delay(DELAY_BEFORE_SHOWING_NOTIFICATION_SCREEN) + commands.emit(Command.Notifications) + } + is NotificationPayload.ParticipantRemove -> { + delay(DELAY_BEFORE_SHOWING_NOTIFICATION_SCREEN) + commands.emit(Command.Notifications) + } + else -> { + viewModelScope.launch { + toasts.emit(payload.toString()) + } } } } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/notifications/NotificationsProvider.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/notifications/NotificationsProvider.kt index 8f46252b19..1b2b54dba5 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/notifications/NotificationsProvider.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/notifications/NotificationsProvider.kt @@ -4,6 +4,7 @@ import com.anytypeio.anytype.core_models.Notification import com.anytypeio.anytype.domain.account.AwaitAccountStartManager import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers import com.anytypeio.anytype.domain.workspace.NotificationsChannel +import com.anytypeio.anytype.presentation.BuildConfig import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -13,6 +14,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.launch +import timber.log.Timber interface NotificationsProvider { val events: StateFlow> @@ -30,6 +32,9 @@ interface NotificationsProvider { init { scope.launch(dispatchers.io) { observe().collect { events -> + if (BuildConfig.DEBUG) { + Timber.d("New notifications: $events") + } _events.value = events } } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/notifications/NotificationsViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/notifications/NotificationsViewModel.kt index 7f958f357f..b8816351db 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/notifications/NotificationsViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/notifications/NotificationsViewModel.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.viewModelScope import com.anytypeio.anytype.analytics.base.Analytics import com.anytypeio.anytype.analytics.base.EventsDictionary import com.anytypeio.anytype.analytics.base.sendEvent +import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.ImportErrorCode import com.anytypeio.anytype.core_models.Notification import com.anytypeio.anytype.core_models.NotificationPayload @@ -38,17 +39,51 @@ class NotificationsViewModel( private fun handleNotification(event: Notification.Event) { val payload = event.notification?.payload - if (payload is NotificationPayload.GalleryImport) { - if (payload.errorCode != ImportErrorCode.NULL) { - state.value = NotificationsScreenState.GalleryInstalledError( - errorCode = payload.errorCode + when (payload) { + is NotificationPayload.GalleryImport -> { + if (payload.errorCode != ImportErrorCode.NULL) { + state.value = NotificationsScreenState.GalleryInstalledError( + errorCode = payload.errorCode + ) + } else { + state.value = NotificationsScreenState.GalleryInstalled( + spaceId = payload.spaceId, + galleryName = payload.name + ) + } + } + is NotificationPayload.RequestToJoin -> { + state.value = NotificationsScreenState.Multiplayer.RequestToJoin( + space = payload.spaceId, + spaceName = payload.spaceName, + identity = payload.identity, + identityName = payload.identityName ) - } else { - state.value = NotificationsScreenState.GalleryInstalled( - spaceId = payload.spaceId, - galleryName = payload.name + } + is NotificationPayload.RequestToLeave -> { + state.value = NotificationsScreenState.Multiplayer.RequestToLeave( + space = payload.spaceId, + spaceName = payload.spaceName, + identity = payload.identity, + name = payload.identityName ) } + is NotificationPayload.ParticipantRequestApproved -> { + state.value = NotificationsScreenState.Multiplayer.MemberRequestApproved( + space = payload.spaceId, + spaceName = payload.spaceName, + isReadOnly = !payload.permissions.isOwnerOrEditor() + ) + } + is NotificationPayload.ParticipantRemove -> { + state.value = NotificationsScreenState.Multiplayer.MemberSpaceRemove( + spaceName = payload.spaceName, + identityName = payload.identityName + ) + } + else -> { + Timber.w("Ignored notification: $payload") + } } } @@ -80,7 +115,7 @@ class NotificationsViewModel( } sealed class Command { - object Dismiss : Command() + data object Dismiss : Command() data class NavigateToSpace(val spaceId: SpaceId) : Command() } } @@ -88,7 +123,7 @@ class NotificationsViewModel( sealed class NotificationsScreenState { - object Hidden : NotificationsScreenState() + data object Hidden : NotificationsScreenState() data class GalleryInstalled( val spaceId: SpaceId, val galleryName: String @@ -96,4 +131,31 @@ sealed class NotificationsScreenState { data class GalleryInstalledError( val errorCode: ImportErrorCode ) : NotificationsScreenState() + sealed class Multiplayer : NotificationsScreenState() { + // Owner + data class RequestToJoin( + val space: SpaceId, + val spaceName: String, + val identity: Id, + val identityName: String + ) : Multiplayer() + // Owner + data class RequestToLeave( + val space: SpaceId, + val spaceName: String, + val identity: Id, + val name: String + ) : Multiplayer() + // Member + data class MemberRequestApproved( + val space: SpaceId, + val spaceName: String, + val isReadOnly: Boolean + ) : Multiplayer() + // Member + data class MemberSpaceRemove( + val spaceName: String, + val identityName: String + ) : Multiplayer() + } } \ No newline at end of file