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

DROID-3659 Multiplayer | Invite link without approve (#2407)

This commit is contained in:
Konstantin Ivanov 2025-05-20 16:39:00 +02:00 committed by GitHub
parent b43fff9472
commit 300709aaa2
Signed by: github
GPG key ID: B5690EEEBB952194
17 changed files with 375 additions and 133 deletions

View file

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

View file

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

View file

@ -0,0 +1,7 @@
package com.anytypeio.anytype.core_models.multiplayer
enum class InviteType(val code: Int) {
MEMBER(0),
GUEST(1),
WITHOUT_APPROVE(2);
}

View file

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

View file

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

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="56dp"
android:height="56dp"
android:viewportWidth="56"
android:viewportHeight="56">
<path
android:pathData="M0.348,48.658C0.116,47.781 0,46.849 0,45.864V22.447C0,21.339 0.077,20.399 0.232,19.63C0.402,18.86 0.697,18.182 1.114,17.597C1.548,17.012 2.144,16.45 2.902,15.911L23.264,1.824C25.028,0.608 26.607,0 28,0C29.409,0 30.987,0.608 32.736,1.824L53.098,15.911C53.872,16.45 54.468,17.012 54.886,17.597C55.304,18.182 55.59,18.86 55.745,19.63C55.915,20.399 56,21.339 56,22.447V45.864C56,46.849 55.884,47.781 55.652,48.658L38.424,33.209L51.101,20.623C51.411,20.299 51.55,19.93 51.519,19.514C51.504,19.098 51.295,18.76 50.892,18.498L30.252,4.342C29.432,3.772 28.681,3.487 28,3.487C27.334,3.487 26.584,3.772 25.748,4.342L5.108,18.498C4.705,18.76 4.489,19.098 4.458,19.514C4.427,19.93 4.574,20.299 4.899,20.623L17.576,33.209L0.348,48.658ZM7.29,53C6.036,53 4.992,52.854 4.156,52.561C3.336,52.269 2.639,51.868 2.066,51.36L24.866,30.945C25.376,30.484 25.887,30.145 26.398,29.929C26.924,29.714 27.458,29.606 28,29.606C28.542,29.606 29.068,29.714 29.579,29.929C30.105,30.145 30.624,30.484 31.134,30.945L53.957,51.383C53.384,51.876 52.68,52.269 51.844,52.561C51.024,52.854 49.987,53 48.733,53H7.29Z"
android:fillColor="@color/palette_system_sky"/>
</vector>

View file

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

View file

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

View file

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

View file

@ -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<SpaceId, SpaceInviteLink>(dispatchers.io) {
override suspend fun doWork(params: SpaceId): SpaceInviteLink = repo.generateSpaceInviteLink(
space = params
) : ResultInteractor<GenerateSpaceInviteLink.Params, SpaceInviteLink>(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
)
}

View file

@ -1357,6 +1357,13 @@
<string name="multiplayer_join_a_space">Join a space</string>
<string name="multiplayer_private_comment_for_a_space_owner">Private comment for a space owner</string>
<string name="multiplayer_request_to_join_explanation">Once the space owner approves your request, you\'ll join the space with the access rights owner determined.</string>
<string name="multiplayer_request_to_join_without_approve_title">Join %1$s</string>
<string name="multiplayer_request_to_join_without_approve_desc">You\'ve been invited to join %1$s, created by %2$s</string>
<string name="multiplayer_request_to_join_without_approve_button">Join Space</string>
<string name="multiplayer_request_to_join_loading_text"> Hang tight — were setting things up for you. This should only take a moment.</string>
<string name="multiplayer_space_request_to_join">Request to join</string>
<string name="multiplayer_space_request_to_join_msg">You\'ve been invited to join %1$s space, created by %2$s. Send a request so space owner can let you in.</string>
<string name="multiplayer_tap_to_write_request_to_join_comment">Tap to write your comment</string>

View file

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

View file

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

View file

@ -114,4 +114,6 @@ typealias MSyncStatusUpdate = Space.SyncStatus.Update
typealias MDeviceNetworkType = anytype.model.DeviceNetworkType
typealias MLinkPreview = anytype.model.LinkPreview
typealias MLinkPreview = anytype.model.LinkPreview
typealias MInviteType = anytype.model.InviteType

View file

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

View file

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

View file

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