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:
parent
d81c36ff92
commit
e77b98697d
12 changed files with 566 additions and 74 deletions
|
@ -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
|
||||
}
|
|
@ -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
|
||||
)
|
||||
}
|
|
@ -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? {
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
)
|
||||
)
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue