diff --git a/app/src/main/java/com/anytypeio/anytype/app/Notifications.kt b/app/src/main/java/com/anytypeio/anytype/app/Notifications.kt
index 01626c1c89..891be2ca8b 100644
--- a/app/src/main/java/com/anytypeio/anytype/app/Notifications.kt
+++ b/app/src/main/java/com/anytypeio/anytype/app/Notifications.kt
@@ -99,6 +99,7 @@ class AnytypeNotificationService @Inject constructor(
)
}
is NotificationPayload.ParticipantRequestApproved -> {
+ Timber.d("Processing participant request approved notification : ${notification}")
val placeholder = context.resources.getString(R.string.untitled)
val title = context.resources.getString(
R.string.multiplayer_notification_member_request_approved
@@ -106,16 +107,26 @@ class AnytypeNotificationService @Inject constructor(
val actionTitle = context.resources.getString(
R.string.multiplayer_notification_go_to_space
)
- val body = if (payload.permissions.isOwnerOrEditor()) {
- context.resources.getString(
- R.string.multiplayer_notification_member_request_approved_with_edit_rights,
- payload.spaceName.ifEmpty { placeholder }
- )
- } else {
- context.resources.getString(
- R.string.multiplayer_notification_member_request_approved_with_read_only_rights,
- payload.spaceName.ifEmpty { placeholder }
- )
+ val permissions = payload.permissions
+ val body = when {
+ permissions == null -> {
+ context.resources.getString(
+ R.string.multiplayer_notification_member_request_approved_unknown_rights,
+ payload.spaceName.ifEmpty { placeholder }
+ )
+ }
+ permissions.isOwnerOrEditor() -> {
+ context.resources.getString(
+ R.string.multiplayer_notification_member_request_approved_with_edit_rights,
+ payload.spaceName.ifEmpty { placeholder }
+ )
+ }
+ else -> {
+ context.resources.getString(
+ R.string.multiplayer_notification_member_request_approved_with_read_only_rights,
+ payload.spaceName.ifEmpty { placeholder }
+ )
+ }
}
val intent = Intent(context, MainActivity::class.java).apply {
putExtra(Relations.SPACE_ID, payload.spaceId.id)
diff --git a/app/src/main/java/com/anytypeio/anytype/ui/multiplayer/RequestJoinSpaceFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/multiplayer/RequestJoinSpaceFragment.kt
index e57bb41181..391cb62af6 100644
--- a/app/src/main/java/com/anytypeio/anytype/ui/multiplayer/RequestJoinSpaceFragment.kt
+++ b/app/src/main/java/com/anytypeio/anytype/ui/multiplayer/RequestJoinSpaceFragment.kt
@@ -72,7 +72,9 @@ class RequestJoinSpaceFragment : BaseBottomSheetComposeFragment() {
return ComposeView(requireContext()).apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
- val bottomSheetState = rememberModalBottomSheetState()
+ val bottomSheetState = rememberModalBottomSheetState(
+ skipPartiallyExpanded = true
+ )
val scope = rememberCoroutineScope()
val launcher = rememberLauncherForActivityResult(
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 3590bf343c..1e68eff56d 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
@@ -10,7 +10,7 @@ data class Notification(
val isLocal: Boolean,
val payload: NotificationPayload,
val space: SpaceId,
- val aclHeadId: String
+ val aclHeadId: String? = null
) {
sealed class Event {
@@ -52,7 +52,7 @@ sealed class NotificationPayload {
data class ParticipantRequestApproved(
val spaceId: SpaceId,
val spaceName: String,
- val permissions: SpaceMemberPermissions
+ val permissions: SpaceMemberPermissions? = null
) : NotificationPayload()
diff --git a/localization/src/main/res/values/strings.xml b/localization/src/main/res/values/strings.xml
index 635f825f4e..9203301776 100644
--- a/localization/src/main/res/values/strings.xml
+++ b/localization/src/main/res/values/strings.xml
@@ -1430,6 +1430,7 @@
%1$s joined %2$s space with edit access rights
Request approved
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. 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.
The space \"%1$s\" is no longer accessible.
You have been removed from the space \"%1$s\", or the space was deleted by the owner.
diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/multiplayer/RequestJoinSpaceViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/multiplayer/RequestJoinSpaceViewModel.kt
index 0d1844ae5b..1fb2552d50 100644
--- a/presentation/src/main/java/com/anytypeio/anytype/presentation/multiplayer/RequestJoinSpaceViewModel.kt
+++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/multiplayer/RequestJoinSpaceViewModel.kt
@@ -7,6 +7,9 @@ import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.analytics.base.EventsDictionary.screenInviteRequest
import com.anytypeio.anytype.analytics.base.EventsDictionary.screenRequestSent
import com.anytypeio.anytype.analytics.base.sendEvent
+import com.anytypeio.anytype.core_models.Notification
+import com.anytypeio.anytype.core_models.NotificationPayload
+import com.anytypeio.anytype.core_models.NotificationStatus
import com.anytypeio.anytype.core_models.multiplayer.MultiplayerError
import com.anytypeio.anytype.core_models.multiplayer.SpaceInviteError
import com.anytypeio.anytype.core_models.multiplayer.SpaceInviteView
@@ -27,6 +30,7 @@ import com.anytypeio.anytype.domain.workspace.SpaceManager
import com.anytypeio.anytype.presentation.common.BaseViewModel
import com.anytypeio.anytype.presentation.common.TypedViewState
import javax.inject.Inject
+import kotlin.random.Random
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -130,54 +134,86 @@ class RequestJoinSpaceViewModel(
}
fun onRequestToJoinClicked() {
- when(val curr = state.value) {
- is TypedViewState.Success -> {
- joinSpaceRequestJob?.cancel()
- joinSpaceRequestJob = viewModelScope.launch {
- val fileKey = spaceInviteResolver.parseFileKey(params.link)
- val contentId = spaceInviteResolver.parseContentId(params.link)
- if (contentId != null && fileKey != null) {
- isRequestInProgress.value = true
- sendJoinSpaceRequest.async(
- SendJoinSpaceRequest.Params(
- space = curr.data.space,
- network = configStorage.getOrNull()?.network,
- inviteFileKey = fileKey,
- inviteContentId = contentId
- )
- ).fold(
- onFailure = { e ->
- Timber.e(e, "Error while sending space join request")
- if (e is MultiplayerError.Generic) {
- commands.emit(Command.ShowGenericMultiplayerError(e))
- } else {
- sendToast(e.msg())
- }
- },
- onSuccess = {
- analytics.sendEvent(eventName = screenRequestSent)
- if (notificator.areNotificationsEnabled) {
- if (!curr.data.withoutApprove) {
- commands.emit(Command.Toast.RequestSent)
- }
- commands.emit(Command.Dismiss)
- } else {
- if (!curr.data.withoutApprove) {
- commands.emit(Command.Toast.RequestSent)
- }
- showEnableNotificationDialog.value = true
- }
- }
- )
- isRequestInProgress.value = false
- }
- }
- } else -> {
- // Do nothing.
+ val currentState = state.value
+ if (currentState !is TypedViewState.Success) return
+
+ joinSpaceRequestJob?.cancel()
+ joinSpaceRequestJob = viewModelScope.launch {
+ val fileKey = spaceInviteResolver.parseFileKey(params.link)
+ val contentId = spaceInviteResolver.parseContentId(params.link)
+
+ if (fileKey == null || contentId == null) {
+ Timber.w("Could not parse invite link in onRequestToJoinClicked: ${params.link}")
+ return@launch
}
+
+ isRequestInProgress.value = true
+
+ val params = SendJoinSpaceRequest.Params(
+ space = currentState.data.space,
+ network = configStorage.getOrNull()?.network,
+ inviteFileKey = fileKey,
+ inviteContentId = contentId
+ )
+
+ sendJoinSpaceRequest.async(params).fold(
+ onFailure = { handleJoinRequestFailure(it) },
+ onSuccess = { handleJoinRequestSuccess(currentState.data) }
+ )
+
+ isRequestInProgress.value = false
}
}
+ private suspend fun handleJoinRequestFailure(error: Throwable) {
+ Timber.e(error, "Error while sending space join request")
+ when (error) {
+ is MultiplayerError.Generic -> commands.emit(Command.ShowGenericMultiplayerError(error))
+ else -> sendToast(error.msg())
+ }
+ }
+
+ private suspend fun handleJoinRequestSuccess(data: SpaceInviteView) {
+ analytics.sendEvent(eventName = screenRequestSent)
+
+ val shouldNotify = data.withoutApprove
+ val notificationsEnabled = notificator.areNotificationsEnabled
+
+ if (shouldNotify) {
+ sendApprovalNotification(data)
+ }
+
+ if (notificationsEnabled) {
+ if (!shouldNotify) {
+ commands.emit(Command.Toast.RequestSent)
+ }
+ commands.emit(Command.Dismiss)
+ } else {
+ if (!shouldNotify) {
+ commands.emit(Command.Toast.RequestSent)
+ }
+ showEnableNotificationDialog.value = true
+ }
+ }
+
+ private fun createApprovalNotification(data: SpaceInviteView): Notification {
+ return Notification(
+ id = Random.nextInt().toString(),
+ createTime = System.currentTimeMillis(),
+ status = NotificationStatus.CREATED,
+ isLocal = true,
+ payload = NotificationPayload.ParticipantRequestApproved(
+ spaceId = data.space,
+ spaceName = data.spaceName
+ ),
+ space = data.space
+ )
+ }
+
+ private fun sendApprovalNotification(data: SpaceInviteView) {
+ notificator.notify(createApprovalNotification(data))
+ }
+
fun onCancelJoinSpaceRequestClicked() {
joinSpaceRequestJob?.cancel()
isRequestInProgress.value = false
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 3304dbdbc1..8c0d46718c 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
@@ -80,7 +80,8 @@ class NotificationsViewModel(
notification = notification.id,
space = payload.spaceId,
spaceName = payload.spaceName,
- isReadOnly = !payload.permissions.isOwnerOrEditor()
+ isReadOnly = payload.permissions == null
+ || payload.permissions?.isOwnerOrEditor() != true
)
}
is NotificationPayload.ParticipantRemove -> {
diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/onboarding/signup/OnboardingMnemonicViewModelTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/onboarding/signup/OnboardingMnemonicViewModelTest.kt
index 8318c4ca98..c4c9a3b6f9 100644
--- a/presentation/src/test/java/com/anytypeio/anytype/presentation/onboarding/signup/OnboardingMnemonicViewModelTest.kt
+++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/onboarding/signup/OnboardingMnemonicViewModelTest.kt
@@ -6,6 +6,7 @@ import com.anytypeio.anytype.core_models.NetworkMode
import com.anytypeio.anytype.core_models.NetworkModeConfig
import com.anytypeio.anytype.domain.auth.interactor.GetMnemonic
import com.anytypeio.anytype.domain.config.ConfigStorage
+import com.anytypeio.anytype.domain.deeplink.PendingIntentStore
import com.anytypeio.anytype.domain.device.NetworkConnectionStatus
import com.anytypeio.anytype.domain.network.NetworkModeProvider
import com.anytypeio.anytype.presentation.util.DefaultCoroutineTestRule
@@ -40,8 +41,11 @@ class OnboardingMnemonicViewModelTest {
@Mock
private lateinit var networkModeProvider: NetworkModeProvider
+ lateinit var pendingIntentStore: PendingIntentStore
+
@Before
fun setup() {
+ pendingIntentStore = PendingIntentStore()
MockitoAnnotations.openMocks(this)
}
@@ -139,7 +143,8 @@ class OnboardingMnemonicViewModelTest {
analytics = analytics,
configStorage = configStorage,
networkModeProvider = networkModeProvider,
- networkConnectionStatus = networkConnectionStatus
+ networkConnectionStatus = networkConnectionStatus,
+ pendingIntentStore = pendingIntentStore
)
}
}
\ No newline at end of file