1
0
Fork 0
mirror of https://github.com/anyproto/anytype-kotlin.git synced 2025-06-08 05:47:05 +09:00

DROID-2397 Multiplayer | Enhancement | Basic layouts and event handling for multiplayer flow notifications (#1090)

This commit is contained in:
Evgenii Kozlov 2024-04-10 11:09:53 +02:00 committed by GitHub
parent bf1d5a269d
commit baafe2028e
Signed by: github
GPG key ID: B5690EEEBB952194
13 changed files with 457 additions and 36 deletions

View file

@ -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

View file

@ -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()

View file

@ -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
)
}

View file

@ -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 = {}
)
}

View file

@ -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 = {}
)
}

View file

@ -10,6 +10,7 @@
<color name="text_button_label">#000000</color>
<color name="background_primary">#000000</color>
<color name="background_notification_primary">#FFFFFF</color>
<color name="background_secondary">#1F1E1D</color>
<color name="background_highlighted">#24DAD7CA</color>

View file

@ -117,6 +117,7 @@
<color name="text_button_label">#FFFFFF</color>
<color name="background_primary">#FFFFFF</color>
<color name="background_notification_primary">#000000</color>
<color name="background_secondary">#FFFFFF</color>
<color name="background_highlighted">#144F4F4F</color>

View file

@ -1354,6 +1354,15 @@
<string name="multiplayer_error_space_deleted">Space deleted</string>
<string name="multiplayer_error_space_not_found">Space not found</string>
<string name="multiplayer_read_only_access_error">Read-only access. Contact space owner for changes.</string>
<string name="multiplayer_notification_member_user_sends_join_request"><b>%1$s</b> requested to join the <b>%2$s</b> space.</string>
<string name="multiplayer_notification_member_user_sends_leave_request"><b>%1$s</b> requested to leave the <b>%2$s</b> space.</string>
<string name="multiplayer_notification_new_join_request">New join request</string>
<string name="multiplayer_notification_member_joined_space_with_read_only_rights"><b>%1$s</b> joined <b>%2$s</b> space with read-only access rights</string>
<string name="multiplayer_notification_member_joined_space_with_editor_only_rights"><b>%1$s</b> joined <b>%2$s</b> space with edit access rights</string>
<string name="multiplayer_notification_member_request_approved_with_read_only_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.</string>
<string name="multiplayer_notification_member_request_approved_with_edit_rights">Your request to join the %1$s space has been approved with edit access rights. The space will be available on your device soon.</string>
<string name="multiplayer_notification_member_removed_from_space">You have been removed from the space, or the space was deleted by the owner.</string>
<string name="multiplayer_notification_view_request">View</string>
<!--endregion-->

View file

@ -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() }
}
}

View file

@ -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 -> {

View file

@ -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())
}
}
}
}

View file

@ -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<List<Notification.Event>>
@ -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
}
}

View file

@ -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()
}
}