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

DROID-2250 Multiplayer | Fix | Show invite link and join/leave requrests only to space owner (#1104)

This commit is contained in:
Evgenii Kozlov 2024-04-11 16:29:16 +02:00 committed by GitHub
parent 9a0432ef45
commit 26e632059f
Signed by: github
GPG key ID: B5690EEEBB952194
6 changed files with 121 additions and 80 deletions

View file

@ -10,6 +10,7 @@ import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.config.ConfigStorage
import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider
import com.anytypeio.anytype.domain.workspace.SpaceManager
import com.anytypeio.anytype.presentation.multiplayer.ShareSpaceViewModel
import com.anytypeio.anytype.ui.multiplayer.ShareSpaceFragment
@ -56,4 +57,5 @@ interface ShareSpaceDependencies : ComponentDependencies {
fun dispatchers(): AppCoroutineDispatchers
fun container(): StorelessSubscriptionContainer
fun config(): ConfigStorage
fun permissions(): UserPermissionProvider
}

View file

@ -102,7 +102,11 @@ fun ShareSpaceScreen(
item {
var isMenuExpanded by remember { mutableStateOf(false) }
Box(modifier = Modifier.fillMaxWidth()) {
Toolbar(title = stringResource(R.string.multiplayer_share_space))
if (isCurrentUserOwner) {
Toolbar(title = stringResource(R.string.multiplayer_sharing))
} else {
Toolbar(title = stringResource(R.string.multiplayer_members))
}
Box(
modifier = Modifier
.align(Alignment.CenterEnd)
@ -162,10 +166,12 @@ fun ShareSpaceScreen(
}
}
}
item {
Section(
title = stringResource(R.string.multiplayer_members_and_requests)
)
if (isCurrentUserOwner) {
item {
Section(
title = stringResource(R.string.multiplayer_members_and_requests)
)
}
}
members.forEachIndexed { index, member ->
item {
@ -450,13 +456,13 @@ private fun SpaceMemberRequest(
Spacer(modifier = Modifier.height(2.dp))
val color = when(request) {
ShareSpaceMemberView.Config.Request.Join -> ThemeColor.PINK
ShareSpaceMemberView.Config.Request.Unjoin -> ThemeColor.RED
ShareSpaceMemberView.Config.Request.Leave -> ThemeColor.RED
}
val text = when(request) {
ShareSpaceMemberView.Config.Request.Join -> stringResource(
id = R.string.multiplayer_joining_requested
)
ShareSpaceMemberView.Config.Request.Unjoin -> stringResource(
ShareSpaceMemberView.Config.Request.Leave -> stringResource(
id = R.string.multiplayer_unjoining_requested
)
}
@ -486,7 +492,7 @@ private fun SpaceMemberRequest(
modifier = Modifier.align(Alignment.CenterVertically)
)
}
ShareSpaceMemberView.Config.Request.Unjoin -> {
ShareSpaceMemberView.Config.Request.Leave -> {
ButtonSecondary(
text = stringResource(R.string.multiplayer_approve_request),
onClick = throttledClick(
@ -531,7 +537,7 @@ fun SpaceUnjoinRequestPreview() {
)
),
icon = SpaceMemberIconView.Placeholder(name = "Konstantin"),
request = ShareSpaceMemberView.Config.Request.Unjoin,
request = ShareSpaceMemberView.Config.Request.Leave,
onApproveUnjoinRequestClicked = {},
onViewRequestClicked = {}
)
@ -581,7 +587,7 @@ fun ShareSpaceScreenPreview() {
Relations.NAME to "Aleksey"
)
),
config = ShareSpaceMemberView.Config.Request.Unjoin,
config = ShareSpaceMemberView.Config.Request.Leave,
icon = SpaceMemberIconView.Placeholder(
name = "Aleksey"
)

View file

@ -1316,6 +1316,7 @@
<string name="multiplayer_tap_to_write_request_to_join_comment">Tap to write your comment</string>
<string name="multiplayer_share_space">Share space</string>
<string name="multiplayer_members_and_requests">Members and requests</string>
<string name="multiplayer_members">Members</string>
<string name="multiplayer_unjoining_requested">Unjoining requested</string>
<string name="multiplayer_joining_requested">Joining requested</string>
<string name="multiplayer_approve_request">Approve</string>

View file

@ -282,7 +282,9 @@ class HomeScreenViewModel(
.async(params = previouslyOpenedWidgetObject)
.fold(
onSuccess = { closed.add(previouslyOpenedWidgetObject) },
onFailure = { Timber.e(it, "Error while closing object from history") }
onFailure = {
Timber.e(it, "Error while closing object from history: $previouslyOpenedWidgetObject")
}
)
}
}

View file

@ -23,6 +23,7 @@ import com.anytypeio.anytype.domain.multiplayer.GetSpaceInviteLink
import com.anytypeio.anytype.domain.multiplayer.RemoveSpaceMembers
import com.anytypeio.anytype.domain.multiplayer.RevokeSpaceInviteLink
import com.anytypeio.anytype.domain.multiplayer.StopSharingSpace
import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider
import com.anytypeio.anytype.domain.`object`.canChangeReaderToWriter
import com.anytypeio.anytype.domain.`object`.canChangeWriterToReader
import com.anytypeio.anytype.presentation.common.BaseViewModel
@ -47,8 +48,9 @@ class ShareSpaceViewModel(
private val changeSpaceMemberPermissions: ChangeSpaceMemberPermissions,
private val stopSharingSpace: StopSharingSpace,
private val container: StorelessSubscriptionContainer,
private val permissions: UserPermissionProvider,
private val getAccount: GetAccount,
private val urlBuilder: UrlBuilder
private val urlBuilder: UrlBuilder,
) : BaseViewModel() {
val members = MutableStateFlow<List<ShareSpaceMemberView>>(emptyList())
@ -62,7 +64,19 @@ class ShareSpaceViewModel(
init {
Timber.d("Share-space init with params: $params")
proceedWithUserPermissions()
proceedWithSubscriptions()
}
private fun proceedWithUserPermissions() {
viewModelScope.launch {
permissions
.observe(space = params.space)
.collect { permission ->
isCurrentUserOwner.value = permission == OWNER
}
}
}
private fun proceedWithSubscriptions() {
@ -78,8 +92,8 @@ class ShareSpaceViewModel(
combine(
container.subscribe(spaceSearchParams),
container.subscribe(spaceMembersSearchParams),
getAccount.asFlow(Unit)
) { spaceResponse, membersResponse, accountId ->
isCurrentUserOwner
) { spaceResponse, membersResponse, isCurrentUserOwner ->
val spaceView = spaceResponse.firstOrNull()?.let { ObjectWrapper.SpaceView(it.map) }
val spaceMembers = membersResponse.map { ObjectWrapper.SpaceMember(it.map) }
@ -92,48 +106,45 @@ class ShareSpaceViewModel(
obj = m,
urlBuilder = urlBuilder,
canChangeWriterToReader = canChangeWriterToReader,
canChangeReaderToWriter = canChangeReaderToWriter
canChangeReaderToWriter = canChangeReaderToWriter,
includeRequests = isCurrentUserOwner
)
}
Triple(spaceView, spaceViewMembers, accountId)
Triple(spaceView, spaceViewMembers, isCurrentUserOwner)
}.catch {
Timber.e(
it, "Error while $SHARE_SPACE_MEMBER_SUBSCRIPTION " +
"and $SHARE_SPACE_SPACE_SUBSCRIPTION subscription"
)
}.collect { (spaceView, spaceViewMembers, accountId) ->
}.collect { (spaceView, spaceViewMembers, isCurrentUserOwner) ->
spaceAccessType.value = spaceView?.spaceAccessType
setShareLinkViewState(spaceView)
proceedWithIsCurrentUserOwner(spaceViewMembers, accountId.id)
setShareLinkViewState(spaceView, isCurrentUserOwner)
members.value = spaceViewMembers
}
}
}
private fun proceedWithIsCurrentUserOwner(
members: List<ShareSpaceMemberView>,
account: Id
private suspend fun setShareLinkViewState(
space: ObjectWrapper.SpaceView?,
isCurrentUserOwner: Boolean
) {
isCurrentUserOwner.value = members.any { member ->
with(member.obj) {
identity.isNotEmpty() && identity == account && permissions == OWNER
}
}
}
private suspend fun setShareLinkViewState(space: ObjectWrapper.SpaceView?) {
shareLinkViewState.value = when (space?.spaceAccessType) {
SpaceAccessType.PRIVATE -> ShareLinkViewState.NotGenerated
SpaceAccessType.SHARED -> {
val link = getSpaceInviteLink.async(params.space)
if (link.isSuccess) {
ShareLinkViewState.Shared(link.getOrThrow().scheme)
} else {
ShareLinkViewState.NotGenerated
if (isCurrentUserOwner) {
shareLinkViewState.value = when (space?.spaceAccessType) {
SpaceAccessType.PRIVATE -> ShareLinkViewState.NotGenerated
SpaceAccessType.SHARED -> {
val link = getSpaceInviteLink.async(params.space)
if (link.isSuccess) {
ShareLinkViewState.Shared(link.getOrThrow().scheme)
} else {
ShareLinkViewState.NotGenerated
}
}
else -> ShareLinkViewState.Init
}
else -> ShareLinkViewState.Init
} else {
ShareLinkViewState.Init
}
}
@ -401,7 +412,8 @@ class ShareSpaceViewModel(
private val approveLeaveSpaceRequest: ApproveLeaveSpaceRequest,
private val container: StorelessSubscriptionContainer,
private val urlBuilder: UrlBuilder,
private val getSpaceInviteLink: GetSpaceInviteLink
private val getSpaceInviteLink: GetSpaceInviteLink,
private val permissions: UserPermissionProvider
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T = ShareSpaceViewModel(
@ -415,7 +427,8 @@ class ShareSpaceViewModel(
urlBuilder = urlBuilder,
getAccount = getAccount,
getSpaceInviteLink = getSpaceInviteLink,
approveLeaveSpaceRequest = approveLeaveSpaceRequest
approveLeaveSpaceRequest = approveLeaveSpaceRequest,
permissions = permissions
) as T
}
@ -455,15 +468,15 @@ data class ShareSpaceMemberView(
) {
sealed class Config {
sealed class Request : Config() {
object Join: Request()
object Unjoin: Request()
data object Join: Request()
data object Leave: Request()
}
sealed class Member: Config() {
object Owner: Member()
object Writer: Member()
object Reader: Member()
object NoPermissions: Member()
object Unknown: Member()
data object Owner: Member()
data object Writer: Member()
data object Reader: Member()
data object NoPermissions: Member()
data object Unknown: Member()
}
}
@ -472,7 +485,8 @@ data class ShareSpaceMemberView(
obj: ObjectWrapper.SpaceMember,
urlBuilder: UrlBuilder,
canChangeWriterToReader: Boolean,
canChangeReaderToWriter: Boolean
canChangeReaderToWriter: Boolean,
includeRequests: Boolean
) : ShareSpaceMemberView? {
val icon = SpaceMemberIconView.icon(
obj = obj,
@ -512,16 +526,26 @@ data class ShareSpaceMemberView(
)
}
}
ParticipantStatus.JOINING -> ShareSpaceMemberView(
obj = obj,
config = Config.Request.Join,
icon = icon
)
ParticipantStatus.REMOVING -> ShareSpaceMemberView(
obj = obj,
config = Config.Request.Unjoin,
icon = icon
)
ParticipantStatus.JOINING -> {
if (includeRequests)
ShareSpaceMemberView(
obj = obj,
config = Config.Request.Join,
icon = icon
)
else
null
}
ParticipantStatus.REMOVING -> {
if (includeRequests)
ShareSpaceMemberView(
obj = obj,
config = Config.Request.Leave,
icon = icon
)
else
null
}
else -> null
}
}

View file

@ -22,8 +22,8 @@ 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_models.Id
import com.anytypeio.anytype.core_models.DEFAULT_SPACE_TYPE
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.PRIVATE_SPACE_TYPE
import com.anytypeio.anytype.core_models.SHARED_SPACE_TYPE
import com.anytypeio.anytype.core_models.SpaceType
@ -116,7 +116,8 @@ fun SpaceSettingsScreen(
}
SHARED_SPACE_TYPE -> {
SharedSpaceSharing(
onManageSharedSpaceClicked = onManageSharedSpaceClicked
onManageSharedSpaceClicked = onManageSharedSpaceClicked,
isUserOwner = state.data.permissions == SpaceMemberPermissions.OWNER
)
}
}
@ -391,7 +392,8 @@ fun PrivateSpaceSharing(
@Composable
fun SharedSpaceSharing(
onManageSharedSpaceClicked: () -> Unit
onManageSharedSpaceClicked: () -> Unit,
isUserOwner: Boolean
) {
Box(
modifier = Modifier
@ -417,7 +419,10 @@ fun SharedSpaceSharing(
) {
Text(
modifier = Modifier.align(Alignment.CenterVertically),
text = stringResource(id = R.string.multiplayer_manage),
text = if (isUserOwner)
stringResource(id = R.string.multiplayer_manage)
else
stringResource(id = R.string.multiplayer_members),
color = colorResource(id = R.color.text_secondary),
style = BodyRegular
)
@ -433,22 +438,6 @@ fun SharedSpaceSharing(
}
}
@Preview
@Composable
fun PrivateSpaceSharingPreview() {
PrivateSpaceSharing(
onSharePrivateSpaceClicked = {}
)
}
@Preview
@Composable
fun SharedSpaceSharingPreview() {
SharedSpaceSharing(
onManageSharedSpaceClicked = {}
)
}
@Composable
fun TypeOfSpace(spaceType: SpaceType?) {
Box(
@ -480,4 +469,21 @@ fun TypeOfSpace(spaceType: SpaceType?) {
)
}
}
}
}
@Preview
@Composable
private fun PrivateSpaceSharingPreview() {
PrivateSpaceSharing(
onSharePrivateSpaceClicked = {}
)
}
@Preview
@Composable
private fun SharedSpaceSharingPreview() {
SharedSpaceSharing(
onManageSharedSpaceClicked = {},
isUserOwner = true
)
}