From 300709aaa272d9590041084c7c5df86d549e4957 Mon Sep 17 00:00:00 2001
From: Konstantin Ivanov <54908981+konstantiniiv@users.noreply.github.com>
Date: Tue, 20 May 2025 16:39:00 +0200
Subject: [PATCH] DROID-3659 Multiplayer | Invite link without approve (#2407)
---
.../anytypeio/anytype/ui/main/MainActivity.kt | 28 +-
.../multiplayer/RequestJoinSpaceFragment.kt | 65 +++--
.../core_models/multiplayer/InviteType.kt | 7 +
.../core_models/multiplayer/Multiplayer.kt | 3 +-
.../core_ui/features/multiplayer/Joining.kt | 240 ++++++++++++------
.../res/drawable/ic_join_without_approve.xml | 9 +
.../auth/repo/block/BlockDataRepository.kt | 13 +-
.../data/auth/repo/block/BlockRemote.kt | 7 +-
.../domain/block/repo/BlockRepository.kt | 8 +-
.../multiplayer/GenerateSpaceInviteLink.kt | 21 +-
localization/src/main/res/values/strings.xml | 7 +
.../middleware/block/BlockMiddleware.kt | 13 +-
.../middleware/interactor/Middleware.kt | 17 +-
.../anytype/middleware/mappers/Alias.kt | 4 +-
.../mappers/ToMiddlewareModelMappers.kt | 7 +
.../multiplayer/RequestJoinSpaceViewModel.kt | 14 +-
.../multiplayer/ShareSpaceViewModel.kt | 45 +++-
17 files changed, 375 insertions(+), 133 deletions(-)
create mode 100644 core-models/src/main/java/com/anytypeio/anytype/core_models/multiplayer/InviteType.kt
create mode 100644 core-ui/src/main/res/drawable/ic_join_without_approve.xml
diff --git a/app/src/main/java/com/anytypeio/anytype/ui/main/MainActivity.kt b/app/src/main/java/com/anytypeio/anytype/ui/main/MainActivity.kt
index f10fce4173..b67c274f5f 100644
--- a/app/src/main/java/com/anytypeio/anytype/ui/main/MainActivity.kt
+++ b/app/src/main/java/com/anytypeio/anytype/ui/main/MainActivity.kt
@@ -460,14 +460,10 @@ class MainActivity : AppCompatActivity(R.layout.activity_main), AppNavigation.Pr
if (BuildConfig.DEBUG) Timber.d("Proceeding with share intent: $intent")
when {
intent.type == Mimetype.MIME_TEXT_PLAIN.value -> {
- val raw = intent.getStringExtra(Intent.EXTRA_TEXT) ?: intent.dataString
- if (raw != null) {
- if (checkDeepLink && DefaultDeepLinkResolver.isDeepLink(raw)) {
- vm.onNewDeepLink(DefaultDeepLinkResolver.resolve(raw))
- } else if (raw.isNotEmpty()) {
- vm.onIntentTextShare(raw)
- }
- }
+ handleTextShare(
+ intent = intent,
+ checkDeepLink = checkDeepLink
+ )
}
intent.type?.startsWith(SHARE_IMAGE_INTENT_PATTERN) == true -> {
proceedWithImageShareIntent(intent)
@@ -485,6 +481,22 @@ class MainActivity : AppCompatActivity(R.layout.activity_main), AppNavigation.Pr
}
}
+ private fun handleTextShare(intent: Intent, checkDeepLink: Boolean) {
+ val raw = intent.getStringExtra(Intent.EXTRA_TEXT) ?: intent.dataString ?: return
+
+ when {
+ checkDeepLink && DefaultDeepLinkResolver.isDeepLink(raw) -> {
+ vm.onNewDeepLink(DefaultDeepLinkResolver.resolve(raw))
+ }
+ raw.isNotEmpty() && !DefaultDeepLinkResolver.isDeepLink(raw) -> {
+ vm.onIntentTextShare(raw)
+ }
+ else -> {
+ Timber.d("handleTextShare, skip handle intent :$raw")
+ }
+ }
+ }
+
private fun proceedWithFileShareIntent(intent: Intent) {
if (intent.action == Intent.ACTION_SEND_MULTIPLE) {
vm.onIntentMultipleFilesShare(intent.parseActionSendMultipleUris())
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 f090b6e433..04fab586d1 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
@@ -8,12 +8,17 @@ import android.view.View
import android.view.ViewGroup
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.navigationBars
+import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material.MaterialTheme
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.compose.ui.res.colorResource
@@ -27,6 +32,8 @@ import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.ext.EMPTY_STRING_VALUE
import com.anytypeio.anytype.core_models.multiplayer.MultiplayerError
import com.anytypeio.anytype.core_ui.features.multiplayer.JoinSpaceScreen
+import com.anytypeio.anytype.core_ui.features.multiplayer.JoinSpaceWithoutApproveScreen
+import com.anytypeio.anytype.core_ui.features.multiplayer.JoiningLoadingState
import com.anytypeio.anytype.core_ui.foundation.AlertConfig
import com.anytypeio.anytype.core_ui.foundation.Announcement
import com.anytypeio.anytype.core_ui.foundation.BUTTON_SECONDARY
@@ -40,7 +47,6 @@ import com.anytypeio.anytype.di.common.componentManager
import com.anytypeio.anytype.presentation.common.TypedViewState
import com.anytypeio.anytype.presentation.multiplayer.RequestJoinSpaceViewModel
import com.anytypeio.anytype.presentation.multiplayer.RequestJoinSpaceViewModel.ErrorView
-import com.anytypeio.anytype.presentation.spaces.Command
import com.anytypeio.anytype.ui.home.HomeScreenFragment
import com.anytypeio.anytype.ui.notifications.NotificationPermissionPromptDialog
import com.anytypeio.anytype.ui.settings.typography
@@ -89,39 +95,64 @@ class RequestJoinSpaceFragment : BaseBottomSheetComposeFragment() {
}
MaterialTheme(typography = typography) {
val showModal = vm.showEnableNotificationDialog.collectAsStateWithLifecycle().value
+ val isLoadingInvite = vm.showLoadingInviteProgress.collectAsStateWithLifecycle().value
when(val state = vm.state.collectAsStateWithLifecycle().value) {
is TypedViewState.Loading, is TypedViewState.Success -> {
val isLoading: Boolean
val spaceName: String
val createdByName: String
+ val withoutApprove : Boolean
if (state is TypedViewState.Loading) {
isLoading = true
spaceName = stringResource(R.string.three_dots_text_placeholder)
createdByName = stringResource(R.string.three_dots_text_placeholder)
+ withoutApprove = false
}
else {
isLoading = vm.isRequestInProgress.collectAsStateWithLifecycle().value
with(state as TypedViewState.Success) {
spaceName = state.data.spaceName
createdByName = state.data.creatorName
+ withoutApprove = state.data.withoutApprove
}
}
- if (!showModal) {
- JoinSpaceScreen(
- isLoading = isLoading,
- onRequestJoinSpaceClicked = vm::onRequestToJoinClicked,
- spaceName = spaceName,
- createdByName = createdByName
- )
- } else {
- ModalBottomSheet(
- onDismissRequest = {
- vm.onNotificationPromptDismissed()
- },
- dragHandle = {},
- containerColor = colorResource(id = R.color.background_secondary),
- sheetState = bottomSheetState
- ) {
+ ModalBottomSheet(
+ modifier = Modifier
+ .fillMaxWidth()
+ .windowInsetsPadding(WindowInsets.navigationBars),
+ onDismissRequest = {
+ if (!isLoadingInvite) {
+ dismiss()
+ }
+ },
+ dragHandle = {},
+ containerColor = colorResource(id = R.color.background_secondary),
+ sheetState = bottomSheetState
+ ) {
+ if (isLoadingInvite) {
+ JoiningLoadingState(
+ onCancelLoadingInviteClicked = {
+ vm.onCancelLoadingInviteClicked()
+ dismiss()
+ }
+ )
+ } else if (!showModal) {
+ if (withoutApprove) {
+ JoinSpaceWithoutApproveScreen(
+ isLoading = isLoading,
+ onRequestJoinSpaceClicked = vm::onRequestToJoinClicked,
+ spaceName = spaceName,
+ createdByName = createdByName
+ )
+ } else {
+ JoinSpaceScreen(
+ isLoading = isLoading,
+ onRequestJoinSpaceClicked = vm::onRequestToJoinClicked,
+ spaceName = spaceName,
+ createdByName = createdByName,
+ )
+ }
+ } else {
Prompt(
title = stringResource(R.string.notifications_prompt_get_notified),
description = stringResource(R.string.notifications_prompt_description),
diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/multiplayer/InviteType.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/multiplayer/InviteType.kt
new file mode 100644
index 0000000000..4b1f3cd1b4
--- /dev/null
+++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/multiplayer/InviteType.kt
@@ -0,0 +1,7 @@
+package com.anytypeio.anytype.core_models.multiplayer
+
+enum class InviteType(val code: Int) {
+ MEMBER(0),
+ GUEST(1),
+ WITHOUT_APPROVE(2);
+}
\ No newline at end of file
diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/multiplayer/Multiplayer.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/multiplayer/Multiplayer.kt
index 32d780aa25..95e56af38f 100644
--- a/core-models/src/main/java/com/anytypeio/anytype/core_models/multiplayer/Multiplayer.kt
+++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/multiplayer/Multiplayer.kt
@@ -13,7 +13,8 @@ data class SpaceInviteView(
val space: SpaceId,
val spaceName: String,
val creatorName: String,
- val spaceIconContentId: String
+ val spaceIconContentId: String,
+ val withoutApprove: Boolean
)
enum class ParticipantStatus(
diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/multiplayer/Joining.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/multiplayer/Joining.kt
index 823b6047d7..637265eeff 100644
--- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/multiplayer/Joining.kt
+++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/multiplayer/Joining.kt
@@ -1,60 +1,35 @@
package com.anytypeio.anytype.core_ui.features.multiplayer
-import androidx.compose.foundation.border
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
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.shape.RoundedCornerShape
-import androidx.compose.material3.OutlinedTextFieldDefaults
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
-import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import com.anytypeio.anytype.core_ui.ColorTextInputCursor
import com.anytypeio.anytype.core_ui.R
+import com.anytypeio.anytype.core_ui.common.DefaultPreviews
import com.anytypeio.anytype.core_ui.extensions.throttledClick
-import com.anytypeio.anytype.core_ui.foundation.AlertConfig
-import com.anytypeio.anytype.core_ui.foundation.AlertIcon
import com.anytypeio.anytype.core_ui.foundation.Dragger
-import com.anytypeio.anytype.core_ui.foundation.GRADIENT_TYPE_BLUE
-import com.anytypeio.anytype.core_ui.views.BodyRegular
import com.anytypeio.anytype.core_ui.views.ButtonPrimaryLoading
+import com.anytypeio.anytype.core_ui.views.ButtonSecondary
import com.anytypeio.anytype.core_ui.views.ButtonSize
-import com.anytypeio.anytype.core_ui.views.Caption1Medium
import com.anytypeio.anytype.core_ui.views.Caption1Regular
import com.anytypeio.anytype.core_ui.views.HeadlineHeading
-import com.anytypeio.anytype.core_ui.views.PreviewTitle2Regular
-
-@Composable
-@Preview
-fun JoinSpaceScreenPreview() {
- JoinSpaceScreen(
- onRequestJoinSpaceClicked = {},
- spaceName = "Anytype Android App",
- createdByName = "Konstantin"
- )
-}
-
-@Composable
-@Preview
-fun JoinSpaceScreenPreviewWithEmptyNames() {
- JoinSpaceScreen(
- onRequestJoinSpaceClicked = {},
- spaceName = "",
- createdByName = ""
- )
-}
+import com.anytypeio.anytype.core_ui.views.Title2
@Composable
fun JoinSpaceScreen(
@@ -64,7 +39,9 @@ fun JoinSpaceScreen(
isLoading: Boolean = false
) {
Column(
- modifier = Modifier.fillMaxWidth()
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp)
) {
Dragger(
modifier = Modifier
@@ -72,22 +49,21 @@ fun JoinSpaceScreen(
.padding(vertical = 6.dp)
)
Spacer(modifier = Modifier.height(16.dp))
- AlertIcon(
- icon = AlertConfig.Icon(
- gradient = GRADIENT_TYPE_BLUE,
- icon = R.drawable.ic_alert_message
- )
+ Image(
+ modifier = Modifier.align(Alignment.CenterHorizontally),
+ painter = painterResource(R.drawable.ic_join_without_approve),
+ contentDescription = "Join without approve"
)
- Spacer(modifier = Modifier.height(16.dp))
+ Spacer(modifier = Modifier.height(15.dp))
Text(
text = stringResource(R.string.multiplayer_join_a_space),
style = HeadlineHeading,
color = colorResource(id = R.color.text_primary),
modifier = Modifier.align(Alignment.CenterHorizontally)
)
- Spacer(modifier = Modifier.height(16.dp))
+ Spacer(modifier = Modifier.height(8.dp))
Text(
- style = BodyRegular,
+ style = Title2,
color = colorResource(id = R.color.text_primary),
modifier = Modifier.padding(horizontal = 48.dp),
text = stringResource(
@@ -97,9 +73,9 @@ fun JoinSpaceScreen(
),
textAlign = TextAlign.Center
)
- Spacer(modifier = Modifier.height(32.dp))
+ Spacer(modifier = Modifier.height(19.dp))
Box(
- modifier = Modifier.padding(start = 20.dp, end = 20.dp)
+ modifier = Modifier
) {
ButtonPrimaryLoading(
onClick = throttledClick(
@@ -124,47 +100,153 @@ fun JoinSpaceScreen(
}
@Composable
-private fun CommentBox(commentInputValue: String): String {
- var commentInputValue1 = commentInputValue
- Box(
+fun JoinSpaceWithoutApproveScreen(
+ onRequestJoinSpaceClicked: () -> Unit,
+ spaceName: String,
+ createdByName: String,
+ isLoading: Boolean = false
+) {
+ Column(
modifier = Modifier
- .height(128.dp)
.fillMaxWidth()
- .padding(horizontal = 20.dp)
- .border(width = 1.dp, color = colorResource(id = R.color.shape_primary))
- .clip(RoundedCornerShape(10.dp))
+ .padding(horizontal = 16.dp)
) {
+ Dragger(
+ modifier = Modifier
+ .align(Alignment.CenterHorizontally)
+ .padding(vertical = 6.dp)
+ )
+ Spacer(modifier = Modifier.height(16.dp))
+ Image(
+ modifier = Modifier.align(Alignment.CenterHorizontally),
+ painter = painterResource(R.drawable.ic_join_without_approve),
+ contentDescription = "Join without approve"
+ )
+ Spacer(modifier = Modifier.height(15.dp))
Text(
- text = stringResource(R.string.multiplayer_private_comment_for_a_space_owner),
- style = Caption1Medium,
- color = colorResource(id = R.color.text_secondary),
- modifier = Modifier
- .fillMaxWidth()
- .padding(start = 16.dp, end = 16.dp, top = 10.dp)
- )
- TextField(
- value = commentInputValue1,
- onValueChange = { commentInputValue1 = it },
- textStyle = PreviewTitle2Regular.copy(
- color = colorResource(id = R.color.text_primary)
+ text = stringResource(
+ R.string.multiplayer_request_to_join_without_approve_title,
+ spaceName
),
+ style = HeadlineHeading,
+ color = colorResource(id = R.color.text_primary),
modifier = Modifier
.fillMaxWidth()
- .padding(top = 31.dp),
- placeholder = {
- Text(
- text = stringResource(R.string.multiplayer_tap_to_write_request_to_join_comment),
- color = colorResource(id = R.color.text_tertiary)
- )
- },
- colors = OutlinedTextFieldDefaults.colors(
- disabledBorderColor = Color.Transparent,
- errorBorderColor = Color.Transparent,
- focusedBorderColor = Color.Transparent,
- unfocusedBorderColor = Color.Transparent,
- cursorColor = ColorTextInputCursor
- )
+ .padding(horizontal = 16.dp),
+ textAlign = TextAlign.Center
)
+ Spacer(modifier = Modifier.height(8.dp))
+ Text(
+ style = Title2,
+ color = colorResource(id = R.color.text_primary),
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp),
+ text = stringResource(
+ id = R.string.multiplayer_request_to_join_without_approve_desc,
+ spaceName.ifEmpty { stringResource(id = R.string.untitled) },
+ createdByName.ifEmpty { stringResource(id = R.string.untitled) }
+ ),
+ textAlign = TextAlign.Center
+ )
+ Spacer(modifier = Modifier.height(19.dp))
+ ButtonPrimaryLoading(
+ onClick = throttledClick(
+ onClick = { onRequestJoinSpaceClicked() }
+ ),
+ size = ButtonSize.Large,
+ text = stringResource(R.string.multiplayer_request_to_join_without_approve_button),
+ modifierButton = Modifier.fillMaxWidth(),
+ loading = isLoading
+ )
+ Spacer(modifier = Modifier.height(8.dp))
+ ButtonSecondary(
+ onClick = throttledClick(
+ onClick = { onRequestJoinSpaceClicked() }
+ ),
+ text = stringResource(R.string.cancel),
+ modifier = Modifier.fillMaxWidth(),
+ size = ButtonSize.Large,
+ )
+ Spacer(modifier = Modifier.height(16.dp))
}
- return commentInputValue1
+}
+
+@Composable
+fun JoiningLoadingState(
+ onCancelLoadingInviteClicked: () -> Unit
+) {
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center
+ ) {
+ Spacer(modifier = Modifier.height(27.dp))
+ CircularProgressIndicator(
+ modifier = Modifier
+ .size(56.dp),
+ color = colorResource(R.color.shape_secondary),
+ trackColor = colorResource(R.color.shape_primary)
+ )
+ Spacer(modifier = Modifier.height(15.dp))
+ Text(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp),
+ color = colorResource(id = R.color.text_secondary),
+ textAlign = TextAlign.Center,
+ text = stringResource(R.string.multiplayer_request_to_join_loading_text),
+ style = Title2
+ )
+ Spacer(modifier = Modifier.height(19.dp))
+ ButtonSecondary(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 12.dp, start = 16.dp, end = 16.dp),
+ onClick = {
+ onCancelLoadingInviteClicked()
+ },
+ size = ButtonSize.Large,
+ text = stringResource(R.string.cancel),
+ )
+ Spacer(modifier = Modifier.height(16.dp))
+ }
+}
+
+@Composable
+@DefaultPreviews
+fun JoinSpaceScreenPreviewLoading() {
+ JoiningLoadingState(
+ onCancelLoadingInviteClicked = {}
+ )
+}
+
+@Composable
+@DefaultPreviews
+fun JoinSpaceScreenPreview() {
+ JoinSpaceScreen(
+ onRequestJoinSpaceClicked = {},
+ spaceName = "Anytype Android App",
+ createdByName = "Konstantin"
+ )
+}
+
+@Composable
+@DefaultPreviews
+fun JoinSpaceScreenPreviewWithEmptyNames() {
+ JoinSpaceScreen(
+ onRequestJoinSpaceClicked = {},
+ spaceName = "",
+ createdByName = ""
+ )
+}
+
+@Composable
+@DefaultPreviews
+fun JoinSpaceScreenPreviewWithoutApprove() {
+ JoinSpaceWithoutApproveScreen(
+ onRequestJoinSpaceClicked = {},
+ spaceName = "",
+ createdByName = ""
+ )
}
\ No newline at end of file
diff --git a/core-ui/src/main/res/drawable/ic_join_without_approve.xml b/core-ui/src/main/res/drawable/ic_join_without_approve.xml
new file mode 100644
index 0000000000..20cfbd8d05
--- /dev/null
+++ b/core-ui/src/main/res/drawable/ic_join_without_approve.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataRepository.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataRepository.kt
index 3d09e43e1e..d90263af0a 100644
--- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataRepository.kt
+++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataRepository.kt
@@ -37,6 +37,7 @@ import com.anytypeio.anytype.core_models.membership.EmailVerificationStatus
import com.anytypeio.anytype.core_models.membership.GetPaymentUrlResponse
import com.anytypeio.anytype.core_models.membership.Membership
import com.anytypeio.anytype.core_models.membership.MembershipTierData
+import com.anytypeio.anytype.core_models.multiplayer.InviteType
import com.anytypeio.anytype.core_models.multiplayer.SpaceInviteLink
import com.anytypeio.anytype.core_models.multiplayer.SpaceInviteView
import com.anytypeio.anytype.core_models.multiplayer.SpaceMemberPermissions
@@ -910,8 +911,16 @@ class BlockDataRepository(
remote.makeSpaceShareable(space)
}
- override suspend fun generateSpaceInviteLink(space: SpaceId): SpaceInviteLink {
- return remote.generateSpaceInviteLink(space)
+ override suspend fun generateSpaceInviteLink(
+ space: SpaceId,
+ inviteType: InviteType,
+ permissions: SpaceMemberPermissions
+ ): SpaceInviteLink {
+ return remote.generateSpaceInviteLink(
+ space = space,
+ inviteType = inviteType,
+ permissions = permissions
+ )
}
override suspend fun revokeSpaceInviteLink(space: SpaceId) {
diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemote.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemote.kt
index 7d422c4ee2..05e06d199d 100644
--- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemote.kt
+++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemote.kt
@@ -37,6 +37,7 @@ import com.anytypeio.anytype.core_models.membership.EmailVerificationStatus
import com.anytypeio.anytype.core_models.membership.GetPaymentUrlResponse
import com.anytypeio.anytype.core_models.membership.Membership
import com.anytypeio.anytype.core_models.membership.MembershipTierData
+import com.anytypeio.anytype.core_models.multiplayer.InviteType
import com.anytypeio.anytype.core_models.multiplayer.SpaceInviteLink
import com.anytypeio.anytype.core_models.multiplayer.SpaceInviteView
import com.anytypeio.anytype.core_models.multiplayer.SpaceMemberPermissions
@@ -398,7 +399,11 @@ interface BlockRemote {
suspend fun deleteRelationOption(command: Command.DeleteRelationOptions)
suspend fun makeSpaceShareable(space: SpaceId)
- suspend fun generateSpaceInviteLink(space: SpaceId) : SpaceInviteLink
+ suspend fun generateSpaceInviteLink(
+ space: SpaceId,
+ inviteType: InviteType,
+ permissions: SpaceMemberPermissions
+ ): SpaceInviteLink
suspend fun revokeSpaceInviteLink(space: SpaceId)
suspend fun approveSpaceRequest(
space: SpaceId,
diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/block/repo/BlockRepository.kt b/domain/src/main/java/com/anytypeio/anytype/domain/block/repo/BlockRepository.kt
index cb76b1a95b..ff238bd938 100644
--- a/domain/src/main/java/com/anytypeio/anytype/domain/block/repo/BlockRepository.kt
+++ b/domain/src/main/java/com/anytypeio/anytype/domain/block/repo/BlockRepository.kt
@@ -37,6 +37,7 @@ import com.anytypeio.anytype.core_models.membership.EmailVerificationStatus
import com.anytypeio.anytype.core_models.membership.GetPaymentUrlResponse
import com.anytypeio.anytype.core_models.membership.Membership
import com.anytypeio.anytype.core_models.membership.MembershipTierData
+import com.anytypeio.anytype.core_models.multiplayer.InviteType
import com.anytypeio.anytype.core_models.multiplayer.SpaceInviteLink
import com.anytypeio.anytype.core_models.multiplayer.SpaceInviteView
import com.anytypeio.anytype.core_models.multiplayer.SpaceMemberPermissions
@@ -44,6 +45,7 @@ import com.anytypeio.anytype.core_models.primitives.Space
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.domain.base.Result
import com.anytypeio.anytype.domain.block.interactor.sets.CreateObjectSet
+import com.anytypeio.anytype.domain.multiplayer.Permissions
import com.anytypeio.anytype.domain.page.Redo
import com.anytypeio.anytype.domain.page.Undo
@@ -443,7 +445,11 @@ interface BlockRepository {
suspend fun deleteRelationOption(command: Command.DeleteRelationOptions)
suspend fun makeSpaceShareable(space: SpaceId)
- suspend fun generateSpaceInviteLink(space: SpaceId) : SpaceInviteLink
+ suspend fun generateSpaceInviteLink(
+ space: SpaceId,
+ inviteType: InviteType,
+ permissions: SpaceMemberPermissions
+ ): SpaceInviteLink
suspend fun revokeSpaceInviteLink(space: SpaceId)
suspend fun approveSpaceRequest(
space: SpaceId,
diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/multiplayer/GenerateSpaceInviteLink.kt b/domain/src/main/java/com/anytypeio/anytype/domain/multiplayer/GenerateSpaceInviteLink.kt
index 41b6fa336e..324c4ace94 100644
--- a/domain/src/main/java/com/anytypeio/anytype/domain/multiplayer/GenerateSpaceInviteLink.kt
+++ b/domain/src/main/java/com/anytypeio/anytype/domain/multiplayer/GenerateSpaceInviteLink.kt
@@ -1,6 +1,8 @@
package com.anytypeio.anytype.domain.multiplayer
+import com.anytypeio.anytype.core_models.multiplayer.InviteType
import com.anytypeio.anytype.core_models.multiplayer.SpaceInviteLink
+import com.anytypeio.anytype.core_models.multiplayer.SpaceMemberPermissions
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.base.ResultInteractor
@@ -8,10 +10,21 @@ import com.anytypeio.anytype.domain.block.repo.BlockRepository
import javax.inject.Inject
class GenerateSpaceInviteLink @Inject constructor(
- private val dispatchers: AppCoroutineDispatchers,
+ dispatchers: AppCoroutineDispatchers,
private val repo: BlockRepository
-): ResultInteractor(dispatchers.io) {
- override suspend fun doWork(params: SpaceId): SpaceInviteLink = repo.generateSpaceInviteLink(
- space = params
+) : ResultInteractor(dispatchers.io) {
+
+ override suspend fun doWork(params: Params): SpaceInviteLink {
+ return repo.generateSpaceInviteLink(
+ space = params.space,
+ inviteType = params.inviteType,
+ permissions = params.permissions
+ )
+ }
+
+ data class Params(
+ val space: SpaceId,
+ val inviteType: InviteType,
+ val permissions: SpaceMemberPermissions
)
}
\ No newline at end of file
diff --git a/localization/src/main/res/values/strings.xml b/localization/src/main/res/values/strings.xml
index b0586c0230..0750f26bdd 100644
--- a/localization/src/main/res/values/strings.xml
+++ b/localization/src/main/res/values/strings.xml
@@ -1357,6 +1357,13 @@
Join a space
Private comment for a space owner
Once the space owner approves your request, you\'ll join the space with the access rights owner determined.
+
+ Join %1$s
+ You\'ve been invited to join %1$s, created by %2$s
+ Join Space
+
+ Hang tight — we’re setting things up for you. This should only take a moment.
+
Request to join
You\'ve been invited to join %1$s space, created by %2$s. Send a request so space owner can let you in.
Tap to write your comment
diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/block/BlockMiddleware.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/block/BlockMiddleware.kt
index 460ed796fb..4f21c3da07 100644
--- a/middleware/src/main/java/com/anytypeio/anytype/middleware/block/BlockMiddleware.kt
+++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/block/BlockMiddleware.kt
@@ -38,6 +38,7 @@ import com.anytypeio.anytype.core_models.membership.EmailVerificationStatus
import com.anytypeio.anytype.core_models.membership.GetPaymentUrlResponse
import com.anytypeio.anytype.core_models.membership.Membership
import com.anytypeio.anytype.core_models.membership.MembershipTierData
+import com.anytypeio.anytype.core_models.multiplayer.InviteType
import com.anytypeio.anytype.core_models.multiplayer.SpaceInviteLink
import com.anytypeio.anytype.core_models.multiplayer.SpaceInviteView
import com.anytypeio.anytype.core_models.multiplayer.SpaceMemberPermissions
@@ -872,8 +873,16 @@ class BlockMiddleware(
middleware.makeSpaceShareable(space = space)
}
- override suspend fun generateSpaceInviteLink(space: SpaceId): SpaceInviteLink {
- return middleware.generateSpaceInviteLink(space)
+ override suspend fun generateSpaceInviteLink(
+ space: SpaceId,
+ inviteType: InviteType,
+ permissions: SpaceMemberPermissions
+ ): SpaceInviteLink {
+ return middleware.generateSpaceInviteLink(
+ space = space,
+ inviteType = inviteType,
+ permissions = permissions
+ )
}
override suspend fun revokeSpaceInviteLink(space: SpaceId) {
diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt
index 0c43bbce2e..f39984ede4 100644
--- a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt
+++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt
@@ -47,6 +47,7 @@ import com.anytypeio.anytype.core_models.membership.EmailVerificationStatus
import com.anytypeio.anytype.core_models.membership.GetPaymentUrlResponse
import com.anytypeio.anytype.core_models.membership.Membership
import com.anytypeio.anytype.core_models.membership.MembershipTierData
+import com.anytypeio.anytype.core_models.multiplayer.InviteType
import com.anytypeio.anytype.core_models.multiplayer.SpaceInviteLink
import com.anytypeio.anytype.core_models.multiplayer.SpaceInviteView
import com.anytypeio.anytype.core_models.multiplayer.SpaceMemberPermissions
@@ -70,6 +71,7 @@ import com.anytypeio.anytype.middleware.mappers.toCoreLinkPreview
import com.anytypeio.anytype.middleware.mappers.toCoreModel
import com.anytypeio.anytype.middleware.mappers.toCoreModelSearchResults
import com.anytypeio.anytype.middleware.mappers.toCoreModels
+import com.anytypeio.anytype.middleware.mappers.toMiddleware
import com.anytypeio.anytype.middleware.mappers.toMiddlewareModel
import com.anytypeio.anytype.middleware.mappers.toMw
import com.anytypeio.anytype.middleware.mappers.toPayload
@@ -2388,16 +2390,22 @@ class Middleware @Inject constructor(
}
@Throws(Exception::class)
- fun generateSpaceInviteLink(space: SpaceId) : SpaceInviteLink {
+ fun generateSpaceInviteLink(
+ space: SpaceId,
+ inviteType: InviteType,
+ permissions: SpaceMemberPermissions
+ ): SpaceInviteLink {
val request = Rpc.Space.InviteGenerate.Request(
- spaceId = space.id
+ spaceId = space.id,
+ inviteType = inviteType.toMiddleware(),
+ permissions = permissions.toMw()
)
logRequestIfDebug(request)
val (response, time) = measureTimedValue { service.spaceInviteGenerate(request) }
logResponseIfDebug(response, time)
return SpaceInviteLink(
contentId = response.inviteCid,
- fileKey= response.inviteFileKey
+ fileKey = response.inviteFileKey
)
}
@@ -2511,7 +2519,8 @@ class Middleware @Inject constructor(
space = SpaceId(response.spaceId),
creatorName = response.creatorName,
spaceName = response.spaceName,
- spaceIconContentId = response.spaceIconCid
+ spaceIconContentId = response.spaceIconCid,
+ withoutApprove = response.inviteType == anytype.model.InviteType.WithoutApprove
)
}
diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/Alias.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/Alias.kt
index cf55b96ef4..5ee4c1f5c8 100644
--- a/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/Alias.kt
+++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/Alias.kt
@@ -114,4 +114,6 @@ typealias MSyncStatusUpdate = Space.SyncStatus.Update
typealias MDeviceNetworkType = anytype.model.DeviceNetworkType
-typealias MLinkPreview = anytype.model.LinkPreview
\ No newline at end of file
+typealias MLinkPreview = anytype.model.LinkPreview
+
+typealias MInviteType = anytype.model.InviteType
\ No newline at end of file
diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToMiddlewareModelMappers.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToMiddlewareModelMappers.kt
index d4f9eeb9a3..a88693493a 100644
--- a/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToMiddlewareModelMappers.kt
+++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToMiddlewareModelMappers.kt
@@ -18,6 +18,7 @@ import com.anytypeio.anytype.core_models.Relation
import com.anytypeio.anytype.core_models.chats.Chat
import com.anytypeio.anytype.core_models.membership.MembershipPaymentMethod
import com.anytypeio.anytype.core_models.membership.NameServiceNameType
+import com.anytypeio.anytype.core_models.multiplayer.InviteType
import com.anytypeio.anytype.core_models.multiplayer.SpaceMemberPermissions
@@ -637,3 +638,9 @@ fun DeviceNetworkType.mw(): MDeviceNetworkType = when(this) {
DeviceNetworkType.NOT_CONNECTED -> MDeviceNetworkType.NOT_CONNECTED
}
+fun InviteType.toMiddleware(): MInviteType = when (this) {
+ InviteType.MEMBER -> MInviteType.Member
+ InviteType.GUEST -> MInviteType.Guest
+ InviteType.WITHOUT_APPROVE -> MInviteType.WithoutApprove
+}
+
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 689d2314f0..4d553bfa74 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
@@ -27,6 +27,8 @@ 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 kotlinx.coroutines.Job
+import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
@@ -50,6 +52,8 @@ class RequestJoinSpaceViewModel(
val isRequestInProgress = MutableStateFlow(false)
val showEnableNotificationDialog = MutableStateFlow(false)
val commands = MutableSharedFlow(0)
+ val showLoadingInviteProgress = MutableStateFlow(false)
+ private var getSpaceInviteViewJob: Job? = null
init {
Timber.i("RequestJoinSpaceViewModel, init")
@@ -60,7 +64,8 @@ class RequestJoinSpaceViewModel(
val fileKey = spaceInviteResolver.parseFileKey(params.link)
val contentId = spaceInviteResolver.parseContentId(params.link)
if (fileKey != null && contentId != null) {
- viewModelScope.launch {
+ showLoadingInviteProgress.value = true
+ getSpaceInviteViewJob = viewModelScope.launch {
getSpaceInviteView.async(
GetSpaceInviteView.Params(
inviteContentId = contentId,
@@ -68,6 +73,7 @@ class RequestJoinSpaceViewModel(
)
).fold(
onSuccess = { view ->
+ showLoadingInviteProgress.value = false
val isAlreadyMember = checkIsUserSpaceMember
.async(view.space)
.getOrDefault(false)
@@ -85,6 +91,7 @@ class RequestJoinSpaceViewModel(
}
},
onFailure = { e ->
+ showLoadingInviteProgress.value = false
if (e is SpaceInviteError) {
when(e) {
is SpaceInviteError.InvalidInvite -> {
@@ -117,6 +124,11 @@ class RequestJoinSpaceViewModel(
}
}
+ fun onCancelLoadingInviteClicked() {
+ getSpaceInviteViewJob?.cancel()
+ showLoadingInviteProgress.value = false
+ }
+
fun onRequestToJoinClicked() {
when(val curr = state.value) {
is TypedViewState.Success -> {
diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/multiplayer/ShareSpaceViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/multiplayer/ShareSpaceViewModel.kt
index de5747f940..1a94beab7a 100644
--- a/presentation/src/main/java/com/anytypeio/anytype/presentation/multiplayer/ShareSpaceViewModel.kt
+++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/multiplayer/ShareSpaceViewModel.kt
@@ -23,6 +23,7 @@ import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.ext.isPossibleToUpgradeNumberOfSpaceMembers
import com.anytypeio.anytype.core_models.membership.TierId
+import com.anytypeio.anytype.core_models.multiplayer.InviteType
import com.anytypeio.anytype.core_models.multiplayer.MultiplayerError
import com.anytypeio.anytype.core_models.multiplayer.ParticipantStatus
import com.anytypeio.anytype.core_models.multiplayer.SpaceAccessType
@@ -203,7 +204,10 @@ class ShareSpaceViewModel(
}
}
- private fun proceedWithGeneratingInviteLink() {
+ private fun proceedWithGeneratingInviteLink(
+ inviteType: InviteType = InviteType.MEMBER,
+ permissions: SpaceMemberPermissions = SpaceMemberPermissions.READER
+ ) {
viewModelScope.launch {
if (spaceAccessType.value == SpaceAccessType.PRIVATE) {
makeSpaceShareable.async(
@@ -212,27 +216,44 @@ class ShareSpaceViewModel(
onSuccess = {
analytics.sendEvent(eventName = EventsDictionary.shareSpace)
Timber.d("Successfully made space shareable")
+ generateInviteLink(
+ inviteType = inviteType,
+ permissions = permissions
+ )
},
onFailure = {
Timber.e(it, "Error while making space shareable")
proceedWithMultiplayerError(it)
}
)
- }
- generateSpaceInviteLink
- .async(vmParams.space)
- .fold(
- onSuccess = { link ->
- shareLinkViewState.value = ShareLinkViewState.Shared(link = link.scheme)
- },
- onFailure = {
- Timber.e(it, "Error while generating invite link")
- proceedWithMultiplayerError(it)
- }
+ } else {
+ generateInviteLink(
+ inviteType = inviteType,
+ permissions = permissions
)
+ }
}
}
+ private suspend fun generateInviteLink(inviteType: InviteType, permissions: SpaceMemberPermissions) {
+ generateSpaceInviteLink.async(
+ params = GenerateSpaceInviteLink.Params(
+ space = vmParams.space,
+ inviteType = inviteType,
+ permissions = permissions
+ )
+ ).fold(
+ onSuccess = { inviteLink ->
+ shareLinkViewState.value = ShareLinkViewState.Shared(inviteLink.scheme)
+ Timber.d("Successfully generated invite link")
+ },
+ onFailure = {
+ Timber.e(it, "Error while generating invite link")
+ proceedWithMultiplayerError(it)
+ }
+ )
+ }
+
fun onShareInviteLinkClicked() {
viewModelScope.launch {
when (val value = shareLinkViewState.value) {