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

DROID-2345 Multiplayer | Enhancement | Support share limit restrictions (#1134)

This commit is contained in:
Evgenii Kozlov 2024-04-18 15:19:16 +02:00 committed by uburoiubu
parent d81c36ff92
commit e77b98697d
No known key found for this signature in database
GPG key ID: C8FB80E0A595FBB6
12 changed files with 566 additions and 74 deletions

View file

@ -15,6 +15,7 @@ import com.anytypeio.anytype.domain.debugging.DebugSpaceContentSaver
import com.anytypeio.anytype.domain.debugging.DebugSpaceShareDownloader
import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.multiplayer.SpaceViewSubscriptionContainer
import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider
import com.anytypeio.anytype.domain.workspace.SpaceManager
import com.anytypeio.anytype.presentation.spaces.SpaceGradientProvider
@ -99,4 +100,5 @@ interface SpaceSettingsDependencies : ComponentDependencies {
fun config(): ConfigStorage
fun context(): Context
fun userPermission(): UserPermissionProvider
fun spaceViewSubscriptionContainer(): SpaceViewSubscriptionContainer
}

View file

@ -1,13 +1,16 @@
package com.anytypeio.anytype.di.main
import com.anytypeio.anytype.di.main.ConfigModule.DEFAULT_APP_COROUTINE_SCOPE
import com.anytypeio.anytype.domain.account.AwaitAccountStartManager
import com.anytypeio.anytype.domain.auth.repo.AuthRepository
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.config.ConfigStorage
import com.anytypeio.anytype.domain.debugging.Logger
import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer
import com.anytypeio.anytype.domain.multiplayer.ActiveSpaceMemberSubscriptionContainer
import com.anytypeio.anytype.domain.multiplayer.DefaultUserPermissionProvider
import com.anytypeio.anytype.domain.multiplayer.SpaceViewSubscriptionContainer
import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider
import com.anytypeio.anytype.domain.objects.DefaultStoreOfObjectTypes
import com.anytypeio.anytype.domain.objects.DefaultStoreOfRelations
@ -126,4 +129,36 @@ object SubscriptionsModule {
repo = repo,
logger = logger
)
@JvmStatic
@Provides
@Singleton
fun spaceViewSubscriptionContainer(
dispatchers: AppCoroutineDispatchers,
@Named(DEFAULT_APP_COROUTINE_SCOPE) scope: CoroutineScope,
container: StorelessSubscriptionContainer,
awaitAccountStartManager: AwaitAccountStartManager
) : SpaceViewSubscriptionContainer = SpaceViewSubscriptionContainer.Default(
dispatchers = dispatchers,
scope = scope,
container = container,
awaitAccountStart = awaitAccountStartManager
)
@JvmStatic
@Provides
@Singleton
fun activeSpaceMemberSubscriptionContainer(
dispatchers: AppCoroutineDispatchers,
@Named(DEFAULT_APP_COROUTINE_SCOPE) scope: CoroutineScope,
container: StorelessSubscriptionContainer,
spaceManager: SpaceManager,
awaitAccountStartManager: AwaitAccountStartManager
) : ActiveSpaceMemberSubscriptionContainer = ActiveSpaceMemberSubscriptionContainer.Default(
dispatchers = dispatchers,
scope = scope,
container = container,
manager = spaceManager,
awaitAccountStart = awaitAccountStartManager
)
}

View file

@ -280,6 +280,13 @@ sealed class ObjectWrapper {
val writersLimit: Double? by default
val readersLimit: Double? by default
val sharedSpaceLimit: Int
get() {
val value = getValue<Double?>(Relations.SHARED_SPACES_LIMIT)
return value?.toInt() ?: 0
}
}
inline fun <reified T> getValue(relation: Key): T? {

View file

@ -89,6 +89,8 @@ object Relations {
const val READERS_LIMIT = "readersLimit"
const val WRITERS_LIMIT = "writersLimit"
const val SHARED_SPACES_LIMIT = "sharedSpacesLimit"
val systemRelationKeys = listOf(
"id",
"name",
@ -146,6 +148,7 @@ object Relations {
"relationReadonlyValue",
"relationDefaultValue",
"relationFormatObjectTypes",
"relationOptionColor"
"relationOptionColor",
"sharedSpacesLimit"
)
}

View file

@ -0,0 +1,117 @@
package com.anytypeio.anytype.domain.multiplayer
import com.anytypeio.anytype.core_models.DVFilter
import com.anytypeio.anytype.core_models.DVFilterCondition
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.domain.account.AwaitAccountStartManager
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.library.StoreSearchParams
import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer
import com.anytypeio.anytype.domain.workspace.SpaceManager
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
interface ActiveSpaceMemberSubscriptionContainer {
fun start()
fun stop()
fun observe() : Flow<List<ObjectWrapper.SpaceMember>>
fun get() : List<ObjectWrapper.SpaceMember>
class Default @Inject constructor(
private val manager: SpaceManager,
private val container: StorelessSubscriptionContainer,
private val scope: CoroutineScope,
private val dispatchers: AppCoroutineDispatchers,
private val awaitAccountStart: AwaitAccountStartManager
) : ActiveSpaceMemberSubscriptionContainer {
private val data = MutableStateFlow<List<ObjectWrapper.SpaceMember>>(emptyList())
private val jobs = mutableListOf<Job>()
init {
scope.launch {
awaitAccountStart.isStarted().collect { isStarted ->
if (isStarted)
start()
else
stop()
}
}
}
override fun observe(): Flow<List<ObjectWrapper.SpaceMember>> {
return data
}
override fun get(): List<ObjectWrapper.SpaceMember> {
return data.value
}
override fun start() {
jobs += scope.launch(dispatchers.io) {
manager
.observe()
.flatMapLatest { config ->
container.subscribe(
StoreSearchParams(
subscription = GLOBAL_SUBSCRIPTION,
filters = buildList {
add(
DVFilter(
relation = Relations.LAYOUT,
value = ObjectType.Layout.PARTICIPANT.code.toDouble(),
condition = DVFilterCondition.EQUAL
)
)
add(
DVFilter(
relation = Relations.SPACE_ID,
condition = DVFilterCondition.EQUAL,
value = config.space
)
)
},
limit = 0,
keys = listOf(
Relations.ID,
Relations.SPACE_ID,
Relations.IDENTITY,
Relations.PARTICIPANT_PERMISSIONS,
Relations.PARTICIPANT_STATUS,
Relations.LAYOUT,
Relations.NAME,
Relations.ICON_IMAGE,
)
)
)
}.map { objects ->
objects.map { obj ->
ObjectWrapper.SpaceMember(obj.map)
}
}.collect {
data.value = it
}
}
}
override fun stop() {
jobs.forEach { it.cancel() }
scope.launch(dispatchers.io) {
container.unsubscribe(listOf(DefaultUserPermissionProvider.GLOBAL_SUBSCRIPTION))
}
}
companion object {
const val GLOBAL_SUBSCRIPTION = "subscription.global.active-space-members"
}
}
}

View file

@ -0,0 +1,160 @@
package com.anytypeio.anytype.domain.multiplayer
import com.anytypeio.anytype.core_models.DVFilter
import com.anytypeio.anytype.core_models.DVFilterCondition
import com.anytypeio.anytype.core_models.DVSort
import com.anytypeio.anytype.core_models.DVSortType
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.core_models.multiplayer.SpaceAccessType
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.core_models.restrictions.SpaceStatus
import com.anytypeio.anytype.domain.account.AwaitAccountStartManager
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.library.StoreSearchParams
import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.launch
interface SpaceViewSubscriptionContainer {
fun start()
fun stop()
fun observe(): Flow<List<ObjectWrapper.SpaceView>>
fun observe(space: SpaceId) : Flow<ObjectWrapper.SpaceView>
fun get(): List<ObjectWrapper.SpaceView>
class Default @Inject constructor(
private val container: StorelessSubscriptionContainer,
private val scope: CoroutineScope,
private val dispatchers: AppCoroutineDispatchers,
private val awaitAccountStart: AwaitAccountStartManager
) : SpaceViewSubscriptionContainer {
private val data = MutableStateFlow<List<ObjectWrapper.SpaceView>>(emptyList())
private val jobs = mutableListOf<Job>()
init {
scope.launch {
awaitAccountStart.isStarted().collect { isStarted ->
if (isStarted)
start()
else
stop()
}
}
}
override fun observe(): Flow<List<ObjectWrapper.SpaceView>> {
return data
}
override fun observe(space: SpaceId): Flow<ObjectWrapper.SpaceView> {
return data.mapNotNull { all ->
all.firstOrNull { spaceView -> spaceView.targetSpaceId == space.id }
}
}
override fun get(): List<ObjectWrapper.SpaceView> {
return data.value
}
override fun start() {
jobs += scope.launch(dispatchers.io) {
container.subscribe(
StoreSearchParams(
subscription = GLOBAL_SUBSCRIPTION,
keys = listOf(
Relations.ID,
Relations.TARGET_SPACE_ID,
Relations.SPACE_ACCOUNT_STATUS,
Relations.SPACE_LOCAL_STATUS,
Relations.SPACE_ACCESS_TYPE,
Relations.SHARED_SPACES_LIMIT,
Relations.READERS_LIMIT,
Relations.WRITERS_LIMIT,
Relations.NAME,
Relations.CREATED_DATE,
Relations.CREATOR,
Relations.ICON_IMAGE,
Relations.ICON_OPTION,
),
filters = listOf(
DVFilter(
relation = Relations.LAYOUT,
value = ObjectType.Layout.SPACE_VIEW.code.toDouble(),
condition = DVFilterCondition.EQUAL
),
DVFilter(
relation = Relations.SPACE_ACCOUNT_STATUS,
value = buildList {
add(SpaceStatus.SPACE_DELETED.code.toDouble())
add(SpaceStatus.SPACE_REMOVING.code.toDouble())
},
condition = DVFilterCondition.NOT_IN
),
DVFilter(
relation = Relations.SPACE_LOCAL_STATUS,
value = SpaceStatus.OK.code.toDouble(),
condition = DVFilterCondition.EQUAL
)
),
sorts = listOf(
DVSort(
relationKey = Relations.LAST_OPENED_DATE,
type = DVSortType.DESC,
includeTime = true
)
)
)
).map { objects ->
objects.map { obj ->
ObjectWrapper.SpaceView(obj.map)
}
}.collect {
data.value = it
}
}
}
override fun stop() {
jobs.forEach { it.cancel() }
scope.launch(dispatchers.io) {
container.unsubscribe(listOf(GLOBAL_SUBSCRIPTION))
}
}
companion object {
const val GLOBAL_SUBSCRIPTION = "subscription.global.space-views"
}
}
}
fun SpaceViewSubscriptionContainer.isSharingLimitReached() : Flow<Boolean> {
val sharedSpacesCount = observe().map { spaceViews ->
spaceViews.count { spaceView ->
spaceView.spaceAccessType == SpaceAccessType.SHARED
}
}
val sharedSpaceLimit = observe().map { spaceViews ->
val defaultSpace = spaceViews.firstOrNull { space ->
space.spaceAccessType == SpaceAccessType.DEFAULT
}
defaultSpace?.sharedSpaceLimit ?: 0
}
return combine(
sharedSpaceLimit,
sharedSpacesCount
) { limit, count ->
limit == 0 || count >= limit
}
}

View file

@ -2,7 +2,6 @@ package com.anytypeio.anytype.domain.spaces
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.core_models.restrictions.SpaceStatus
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.config.ConfigStorage
import com.anytypeio.anytype.domain.debugging.Logger
@ -78,6 +77,6 @@ class SpaceDeletedStatusWatcher @Inject constructor(
}
companion object {
const val GLOBAL_SPACE_VIEW_SUBSCRIPTION = "subscription.global.space-view"
const val GLOBAL_SPACE_VIEW_SUBSCRIPTION = "subscription.global.space-view-deleted-status-watcher"
}
}

View file

@ -0,0 +1,181 @@
package com.anytypeio.anytype.domain.multiplayer
import app.cash.turbine.test
import com.anytypeio.anytype.core_models.StubSpaceView
import com.anytypeio.anytype.core_models.multiplayer.SpaceAccessType
import kotlin.test.assertEquals
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.stub
class SpaceViewSubscriptionContainerTest {
@Mock
lateinit var container: SpaceViewSubscriptionContainer
@Before
fun before() {
MockitoAnnotations.openMocks(this)
}
@Test
fun `should reach limit if limit is 0`() = runTest {
val defaultSpaceView = StubSpaceView(
sharedSpaceLimit = 0,
spaceAccessType = SpaceAccessType.DEFAULT
)
val privateSpaceView = StubSpaceView(
sharedSpaceLimit = 0,
spaceAccessType = SpaceAccessType.PRIVATE
)
container.stub {
on {
observe()
} doReturn flowOf(
listOf(defaultSpaceView, privateSpaceView)
)
}
container.isSharingLimitReached().test {
val result = awaitItem()
assertEquals(
expected = true,
actual = result
)
awaitComplete()
}
}
@Test
fun `should not reach limit if limit is 1 and there is one private space and zero shared spaces`() = runTest {
val defaultSpaceView = StubSpaceView(
sharedSpaceLimit = 1,
spaceAccessType = SpaceAccessType.DEFAULT
)
val privateSpaceView = StubSpaceView(
sharedSpaceLimit = 0,
spaceAccessType = SpaceAccessType.PRIVATE
)
container.stub {
on {
observe()
} doReturn flowOf(
listOf(defaultSpaceView, privateSpaceView)
)
}
container.isSharingLimitReached().test {
val result = awaitItem()
assertEquals(
expected = false,
actual = result
)
awaitComplete()
}
}
@Test
fun `should reach limit if limit is 1 and there is one shared space already`() = runTest {
val defaultSpaceView = StubSpaceView(
sharedSpaceLimit = 1,
spaceAccessType = SpaceAccessType.DEFAULT
)
val privateSpaceView = StubSpaceView(
sharedSpaceLimit = 0,
spaceAccessType = SpaceAccessType.SHARED
)
container.stub {
on {
observe()
} doReturn flowOf(
listOf(defaultSpaceView, privateSpaceView)
)
}
container.isSharingLimitReached().test {
val result = awaitItem()
assertEquals(
expected = true,
actual = result
)
awaitComplete()
}
}
@Test
fun `should not reach limit if limit is 2 and there is one shared space already`() = runTest {
val defaultSpaceView = StubSpaceView(
sharedSpaceLimit = 2,
spaceAccessType = SpaceAccessType.DEFAULT
)
val privateSpaceView = StubSpaceView(
sharedSpaceLimit = 0,
spaceAccessType = SpaceAccessType.SHARED
)
container.stub {
on {
observe()
} doReturn flowOf(
listOf(defaultSpaceView, privateSpaceView)
)
}
container.isSharingLimitReached().test {
val result = awaitItem()
assertEquals(
expected = false,
actual = result
)
awaitComplete()
}
}
@Test
fun `should consider share limits from default space is one private space already`() = runTest {
val defaultSpaceView = StubSpaceView(
sharedSpaceLimit = 0,
spaceAccessType = SpaceAccessType.DEFAULT
)
val privateSpaceView = StubSpaceView(
sharedSpaceLimit = 2,
spaceAccessType = SpaceAccessType.PRIVATE
)
container.stub {
on {
observe()
} doReturn flowOf(
listOf(defaultSpaceView, privateSpaceView)
)
}
container.isSharingLimitReached().test {
val result = awaitItem()
assertEquals(
expected = true,
actual = result
)
awaitComplete()
}
}
}

View file

@ -13,7 +13,6 @@ import com.anytypeio.anytype.core_models.ObjectTypeUniqueKeys
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.core_models.primitives.TypeKey
import com.anytypeio.anytype.domain.library.StoreSearchParams
import com.anytypeio.anytype.presentation.multiplayer.ShareSpaceViewModel
import com.anytypeio.anytype.presentation.objects.SupportedLayouts
/**
@ -1132,11 +1131,13 @@ object ObjectSearchConstants {
Relations.SPACE_ACCOUNT_STATUS,
Relations.SPACE_ACCESS_TYPE,
Relations.SPACE_LOCAL_STATUS,
Relations.SHARED_SPACES_LIMIT,
Relations.READERS_LIMIT,
Relations.WRITERS_LIMIT
Relations.WRITERS_LIMIT,
)
//region SPACE VIEW
fun getSpaceViewSearchParams(subscription: String, targetSpaceId: Id): StoreSearchParams {
return StoreSearchParams(
subscription = subscription,

View file

@ -8,11 +8,8 @@ import com.anytypeio.anytype.analytics.base.EventsDictionary
import com.anytypeio.anytype.analytics.base.EventsPropertiesKey
import com.anytypeio.anytype.analytics.base.sendEvent
import com.anytypeio.anytype.analytics.props.Props
import com.anytypeio.anytype.core_models.DVFilter
import com.anytypeio.anytype.core_models.DVFilterCondition
import com.anytypeio.anytype.core_models.Filepath
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.core_models.SpaceType
@ -25,10 +22,10 @@ import com.anytypeio.anytype.core_utils.ui.ViewState
import com.anytypeio.anytype.domain.base.fold
import com.anytypeio.anytype.domain.config.ConfigStorage
import com.anytypeio.anytype.domain.debugging.DebugSpaceShareDownloader
import com.anytypeio.anytype.domain.library.StoreSearchParams
import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.multiplayer.SpaceViewSubscriptionContainer
import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider
import com.anytypeio.anytype.domain.multiplayer.isSharingLimitReached
import com.anytypeio.anytype.domain.spaces.DeleteSpace
import com.anytypeio.anytype.domain.spaces.SetSpaceDetails
import com.anytypeio.anytype.domain.workspace.SpaceManager
@ -37,7 +34,6 @@ import javax.inject.Inject
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.launch
import timber.log.Timber
@ -46,14 +42,14 @@ class SpaceSettingsViewModel(
private val analytics: Analytics,
private val setSpaceDetails: SetSpaceDetails,
private val spaceManager: SpaceManager,
private val container: StorelessSubscriptionContainer,
private val gradientProvider: SpaceGradientProvider,
private val urlBuilder: UrlBuilder,
private val deleteSpace: DeleteSpace,
private val configStorage: ConfigStorage,
private val debugSpaceShareDownloader: DebugSpaceShareDownloader,
private val spaceGradientProvider: SpaceGradientProvider,
private val userPermissionProvider: UserPermissionProvider
private val userPermissionProvider: UserPermissionProvider,
private val container: SpaceViewSubscriptionContainer
): BaseViewModel() {
val commands = MutableSharedFlow<Command>()
@ -70,68 +66,35 @@ class SpaceSettingsViewModel(
eventName = EventsDictionary.screenSettingSpacesSpaceIndex
)
}
proceedWithFetchingSpaceMetaData()
proceedWithObservingSpaceView()
}
private fun proceedWithFetchingSpaceMetaData() {
private fun proceedWithObservingSpaceView() {
viewModelScope.launch {
val config = spaceManager.getConfig(params.space)
container.subscribe(
StoreSearchParams(
subscription = SPACE_SETTINGS_SUBSCRIPTION,
filters = buildList {
add(
DVFilter(
relation = Relations.TARGET_SPACE_ID,
value = params.space.id,
condition = DVFilterCondition.EQUAL
)
)
add(
DVFilter(
relation = Relations.LAYOUT,
value = ObjectType.Layout.SPACE_VIEW.code.toDouble(),
condition = DVFilterCondition.EQUAL
)
)
},
keys = listOf(
Relations.ID,
Relations.SPACE_ID,
Relations.NAME,
Relations.ICON_EMOJI,
Relations.ICON_IMAGE,
Relations.ICON_OPTION,
Relations.CREATED_DATE,
Relations.CREATOR,
Relations.TARGET_SPACE_ID,
Relations.SPACE_ACCESS_TYPE,
Relations.SPACE_LOCAL_STATUS,
Relations.SPACE_ACCOUNT_STATUS
),
limit = 1
)
).mapNotNull { results ->
results.firstOrNull()
}.combine(userPermissionProvider.observe(params.space)) { wrapper, permission ->
val spaceView = ObjectWrapper.SpaceView(wrapper.map)
combine(
container.observe(params.space),
userPermissionProvider.observe(params.space),
container.isSharingLimitReached()
) { spaceView, permission, shareLimitReached ->
SpaceData(
name = wrapper.name.orEmpty(),
icon = wrapper.spaceIcon(
name = spaceView.name.orEmpty(),
icon = spaceView.spaceIcon(
builder = urlBuilder,
spaceGradientProvider = gradientProvider
),
createdDateInMillis = wrapper
createdDateInMillis = spaceView
.getValue<Double?>(Relations.CREATED_DATE)
?.let { timeInSeconds -> (timeInSeconds * 1000L).toLong() },
createdBy = wrapper
createdBy = spaceView
.getValue<Id?>(Relations.CREATOR)
.toString(),
spaceId = params.space.id,
network = config?.network.orEmpty(),
isDeletable = resolveIsSpaceDeletable(spaceView),
spaceType = spaceView.spaceAccessType?.asSpaceType() ?: UNKNOWN_SPACE_TYPE,
permissions = permission ?: SpaceMemberPermissions.NO_PERMISSIONS
permissions = permission ?: SpaceMemberPermissions.NO_PERMISSIONS,
shareLimitReached = shareLimitReached
)
}.collect { spaceData ->
spaceViewState.value = ViewState.Success(spaceData)
@ -297,9 +260,12 @@ class SpaceSettingsViewModel(
fun onSharePrivateSpaceClicked() {
viewModelScope.launch {
commands.emit(
Command.SharePrivateSpace(params.space)
)
val data = spaceViewState.value
if (data is ViewState.Success && !data.data.shareLimitReached) {
commands.emit(
Command.SharePrivateSpace(params.space)
)
}
}
}
@ -312,7 +278,8 @@ class SpaceSettingsViewModel(
val icon: SpaceIconView,
val isDeletable: Boolean = false,
val spaceType: SpaceType,
val permissions: SpaceMemberPermissions
val permissions: SpaceMemberPermissions,
val shareLimitReached: Boolean = false
)
sealed class Command {
@ -326,7 +293,7 @@ class SpaceSettingsViewModel(
class Factory @Inject constructor(
private val params: Params,
private val analytics: Analytics,
private val container: StorelessSubscriptionContainer,
private val container: SpaceViewSubscriptionContainer,
private val urlBuilder: UrlBuilder,
private val setSpaceDetails: SetSpaceDetails,
private val gradientProvider: SpaceGradientProvider,
@ -360,6 +327,5 @@ class SpaceSettingsViewModel(
companion object {
const val SPACE_DEBUG_MSG = "Kindly share this debug logs with Anytype developers."
const val SPACE_SETTINGS_SUBSCRIPTION = "subscription.space-settings.space-views"
}
}

View file

@ -1,5 +1,6 @@
package com.anytypeio.anytype.core_models
import com.anytypeio.anytype.core_models.multiplayer.SpaceAccessType
import com.anytypeio.anytype.test_utils.MockDataFactory
fun StubDataView(
@ -92,4 +93,19 @@ fun StubFilter(
condition = condition,
quickOption = quickOption,
value = value
)
fun StubSpaceView(
id: Id = MockDataFactory.randomUuid(),
targetSpaceId: Id = MockDataFactory.randomUuid(),
spaceAccessType: SpaceAccessType = SpaceAccessType.DEFAULT,
sharedSpaceLimit: Int? = null
) = ObjectWrapper.SpaceView(
map = mapOf(
Relations.ID to id,
Relations.TARGET_SPACE_ID to targetSpaceId,
Relations.SPACE_ACCESS_TYPE to spaceAccessType.code.toDouble(),
Relations.SHARED_SPACES_LIMIT to sharedSpaceLimit?.toDouble()
)
)

View file

@ -107,7 +107,8 @@ fun SpaceSettingsScreen(
}
PRIVATE_SPACE_TYPE -> {
PrivateSpaceSharing(
onSharePrivateSpaceClicked = onSharePrivateSpaceClicked
onSharePrivateSpaceClicked = onSharePrivateSpaceClicked,
shareLimitReached = state.data.shareLimitReached
)
}
SHARED_SPACE_TYPE -> {
@ -344,7 +345,8 @@ fun SpaceSettingsScreenPreview() {
@Composable
fun PrivateSpaceSharing(
onSharePrivateSpaceClicked: () -> Unit
onSharePrivateSpaceClicked: () -> Unit,
shareLimitReached: Boolean
) {
Box(
modifier = Modifier
@ -367,12 +369,14 @@ fun PrivateSpaceSharing(
Row(
modifier = Modifier.align(Alignment.CenterEnd)
) {
Text(
modifier = Modifier.align(Alignment.CenterVertically),
text = stringResource(id = R.string.multiplayer_share),
color = colorResource(id = R.color.text_secondary),
style = BodyRegular
)
if (!shareLimitReached) {
Text(
modifier = Modifier.align(Alignment.CenterVertically),
text = stringResource(id = R.string.multiplayer_share),
color = colorResource(id = R.color.text_secondary),
style = BodyRegular
)
}
Spacer(Modifier.width(10.dp))
Image(
painter = painterResource(R.drawable.ic_arrow_forward),
@ -469,7 +473,8 @@ fun TypeOfSpace(spaceType: SpaceType?) {
@Composable
private fun PrivateSpaceSharingPreview() {
PrivateSpaceSharing(
onSharePrivateSpaceClicked = {}
onSharePrivateSpaceClicked = {},
shareLimitReached = false
)
}