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

DROID-2378 Membership | Enhancement | Introduce incentives on join-space screen (#1304)

This commit is contained in:
Konstantin Ivanov 2024-06-24 19:32:57 +02:00 committed by GitHub
parent 9962283c58
commit 2c6aa40a1e
Signed by: github
GPG key ID: B5690EEEBB952194
38 changed files with 818 additions and 436 deletions

View file

@ -218,6 +218,11 @@ object EventsDictionary {
const val shareTypeShareQr = "ShareQr"
}
object SharingInviteRequest {
const val reader = "Read"
const val writer = "Write"
}
// Network mode
const val selectNetwork = "SelectNetwork"
const val uploadNetworkConfiguration = "UploadNetworkConfiguration"

View file

@ -1123,7 +1123,7 @@ class ComponentManager(
.build()
}
val spaceJoinRequestComponent = ComponentWithParams { params: SpaceJoinRequestViewModel.Params ->
val spaceJoinRequestComponent = ComponentWithParams { params: SpaceJoinRequestViewModel.VmParams ->
DaggerSpaceJoinRequestComponent
.builder()
.withDependencies(findComponentDependencies())

View file

@ -8,9 +8,10 @@ import com.anytypeio.anytype.di.common.ComponentDependencies
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.block.repo.BlockRepository
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.analytics.AnalyticSpaceHelperDelegate
import com.anytypeio.anytype.presentation.membership.provider.MembershipProvider
import com.anytypeio.anytype.presentation.multiplayer.SpaceJoinRequestViewModel
import com.anytypeio.anytype.ui.multiplayer.SpaceJoinRequestFragment
import dagger.Binds
@ -32,7 +33,7 @@ interface SpaceJoinRequestComponent {
interface Builder {
fun withDependencies(dependencies: SpaceJoinRequestDependencies): Builder
@BindsInstance
fun withParams(params: SpaceJoinRequestViewModel.Params): Builder
fun withParams(params: SpaceJoinRequestViewModel.VmParams): Builder
fun build(): SpaceJoinRequestComponent
}
@ -55,8 +56,9 @@ interface SpaceJoinRequestDependencies : ComponentDependencies {
fun blockRepository(): BlockRepository
fun urlBuilder(): UrlBuilder
fun dispatchers(): AppCoroutineDispatchers
fun spaceManager(): SpaceManager
fun analytics(): Analytics
fun analyticSpaceHelper(): AnalyticSpaceHelperDelegate
fun userPermissions(): UserPermissionProvider
fun permissions(): UserPermissionProvider
fun provideSpaceViewContainer(): SpaceViewSubscriptionContainer
fun provideMembershipProvider(): MembershipProvider
}

View file

@ -11,6 +11,8 @@ import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.core.os.bundleOf
import androidx.fragment.app.viewModels
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.fragment.findNavController
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.core_ui.features.multiplayer.SpaceJoinRequestScreen
@ -42,22 +44,13 @@ class SpaceJoinRequestFragment : BaseBottomSheetComposeFragment() {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
MaterialTheme(typography = typography) {
when(val state = vm.viewState.collectAsStateWithLifecycle().value) {
SpaceJoinRequestViewModel.ViewState.Error -> {
// TODO Send toast.
}
SpaceJoinRequestViewModel.ViewState.Init -> {
// Draw nothing.
}
is SpaceJoinRequestViewModel.ViewState.Success -> {
SpaceJoinRequestScreen(
state = state,
onAddViewerClicked = vm::onJoinAsReaderClicked,
onAddEditorClicked = vm::onJoinAsEditorClicked,
onRejectClicked = vm::onRejectRequestClicked
)
}
}
SpaceJoinRequestScreen(
state = vm.viewState.collectAsStateWithLifecycle().value,
onAddViewerClicked = vm::onJoinAsReaderClicked,
onAddEditorClicked = vm::onJoinAsEditorClicked,
onRejectClicked = vm::onRejectRequestClicked,
onUpgradeClicked = vm::onUpgradeClicked
)
LaunchedEffect(Unit) {
vm.toasts.collect { toast(it) }
}
@ -66,14 +59,30 @@ class SpaceJoinRequestFragment : BaseBottomSheetComposeFragment() {
if (isDismissed) dismiss()
}
}
LaunchedEffect(Unit) {
vm.commands.collect { command ->
proceedWithCommand(command)
}
}
}
}
}
}
private fun proceedWithCommand(command: SpaceJoinRequestViewModel.Command?) {
when(command) {
SpaceJoinRequestViewModel.Command.NavigateToMembership -> {
findNavController().navigate(R.id.paymentsScreen)
}
null -> {
// Do nothing.
}
}
}
override fun injectDependencies() {
componentManager().spaceJoinRequestComponent.get(
SpaceJoinRequestViewModel.Params(
SpaceJoinRequestViewModel.VmParams(
space = SpaceId(space),
member = member,
route = analyticsRoute

View file

@ -0,0 +1,40 @@
package com.anytypeio.anytype.core_models.ext
import com.anytypeio.anytype.core_models.membership.MembershipConstants
import com.anytypeio.anytype.core_models.membership.MembershipUpgradeReason
import com.anytypeio.anytype.core_models.membership.TierId
fun TierId.isPossibleToUpgrade(
reason: MembershipUpgradeReason
): Boolean {
return when (reason) {
MembershipUpgradeReason.NumberOfEditors -> isPossibleToUpgradeNumberOfSpaceMembers()
MembershipUpgradeReason.NumberOfReaders -> isPossibleToUpgradeNumberOfSpaceMembers()
//24-07-24 https://linear.app/anytype/issue/PROD-1368/[part-1]-release-4-or-payment-for-the-end-user
//not possible to upgrade the number of shared spaces
MembershipUpgradeReason.NumberOfSharedSpaces -> false
MembershipUpgradeReason.StorageSpace -> isPossibleToUpgradeStorageSpace()
}
}
fun TierId.isPossibleToUpgradeNumberOfSpaceMembers(): Boolean {
return when (this.value) {
MembershipConstants.NONE_ID -> true
MembershipConstants.EXPLORER_ID -> true
MembershipConstants.BUILDER_ID -> false
MembershipConstants.CO_CREATOR_ID -> false
MembershipConstants.ANY_TEAM_ID -> false
else -> true
}
}
fun TierId.isPossibleToUpgradeStorageSpace(): Boolean {
return when (this.value) {
MembershipConstants.NONE_ID -> true
MembershipConstants.EXPLORER_ID -> true
MembershipConstants.BUILDER_ID -> true
MembershipConstants.CO_CREATOR_ID -> false
MembershipConstants.ANY_TEAM_ID -> false
else -> true
}
}

View file

@ -1,4 +1,4 @@
package com.anytypeio.anytype.payments.constants
package com.anytypeio.anytype.core_models.membership
object MembershipConstants {

View file

@ -74,3 +74,9 @@ enum class EmailVerificationStatus {
STATUS_VERIFIED
}
sealed class MembershipUpgradeReason {
data object NumberOfReaders : MembershipUpgradeReason()
data object NumberOfEditors : MembershipUpgradeReason()
data object NumberOfSharedSpaces : MembershipUpgradeReason()
data object StorageSpace : MembershipUpgradeReason()
}

View file

@ -1,8 +1,6 @@
package com.anytypeio.anytype.presentation.membership.models
package com.anytypeio.anytype.core_models.membership
import com.anytypeio.anytype.core_models.membership.MembershipPaymentMethod
import com.anytypeio.anytype.core_models.membership.Membership.Status
import com.anytypeio.anytype.core_models.membership.MembershipTierData
data class MembershipStatus(
val activeTier: TierId,

View file

@ -1,16 +1,21 @@
package com.anytypeio.anytype.core_ui.features.multiplayer
import androidx.compose.foundation.background
import android.annotation.SuppressLint
import android.content.res.Configuration
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@ -20,43 +25,72 @@ 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_models.Id
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.extensions.throttledClick
import com.anytypeio.anytype.core_ui.foundation.Dragger
import com.anytypeio.anytype.core_ui.views.BodyCalloutRegular
import com.anytypeio.anytype.core_ui.views.BodyRegular
import com.anytypeio.anytype.core_ui.views.ButtonMedium
import com.anytypeio.anytype.core_ui.views.ButtonSecondary
import com.anytypeio.anytype.core_ui.views.ButtonSize
import com.anytypeio.anytype.core_ui.views.ButtonUpgrade
import com.anytypeio.anytype.core_ui.views.ButtonWarning
import com.anytypeio.anytype.core_ui.views.HeadlineHeading
import com.anytypeio.anytype.presentation.multiplayer.InviteButton
import com.anytypeio.anytype.presentation.multiplayer.SpaceJoinRequestViewModel.ViewState
import com.anytypeio.anytype.presentation.objects.SpaceMemberIconView
@Preview
@SuppressLint("UnusedBoxWithConstraintsScope")
@Preview(
backgroundColor = 0x0AAEED,
showBackground = true,
uiMode = Configuration.UI_MODE_NIGHT_NO,
name = "Light Mode"
)
@Preview(
backgroundColor = 0x000000,
showBackground = true,
uiMode = Configuration.UI_MODE_NIGHT_YES,
name = "Dark Mode"
)
@Composable
fun SpaceJoinRequestScreenPreview() {
SpaceJoinRequestScreen(
onAddEditorClicked = {},
onAddViewerClicked = {},
onRejectClicked = {},
onUpgradeClicked = {},
state = ViewState.Success(
memberName = "Merk",
newMember = "1",
newMemberName = "Merk",
spaceName = "Investors",
icon = SpaceMemberIconView.Placeholder("Merk"),
canAddAsReader = false,
canAddAsEditor = true
buttons = listOf(
InviteButton.JOIN_AS_VIEWER,
InviteButton.JOIN_AS_VIEWER_DISABLED,
InviteButton.JOIN_AS_EDITOR,
InviteButton.JOIN_AS_EDITOR_DISABLED,
InviteButton.ADD_MORE_VIEWERS,
InviteButton.ADD_MORE_EDITORS,
InviteButton.UPGRADE,
InviteButton.REJECT
)
)
)
}
@Composable
fun SpaceJoinRequestScreen(
state: ViewState.Success,
onAddViewerClicked: () -> Unit,
onAddEditorClicked: () -> Unit,
onRejectClicked: () -> Unit,
state: ViewState,
onAddViewerClicked: (Id) -> Unit,
onAddEditorClicked: (Id) -> Unit,
onRejectClicked: (Id) -> Unit,
onUpgradeClicked: () -> Unit
) {
Column(
modifier = Modifier.fillMaxWidth()
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
) {
Dragger(
modifier = Modifier
@ -64,97 +98,201 @@ fun SpaceJoinRequestScreen(
.padding(vertical = 6.dp)
)
Spacer(modifier = Modifier.height(20.dp))
Box(
modifier = Modifier
.fillMaxWidth()
) {
SpaceMemberIcon(
icon = state.icon,
modifier = Modifier.align(Alignment.Center),
iconSize = 72.dp
)
when (state) {
ViewState.Init -> {
AnimatedVisibility(
visible = state is ViewState.Init,
enter = fadeIn(),
exit = fadeOut()
) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(346.dp),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator(
modifier = Modifier
.size(24.dp),
color = colorResource(R.color.shape_secondary),
trackColor = colorResource(R.color.shape_primary)
)
}
}
}
is ViewState.Success -> {
Box(
modifier = Modifier
.fillMaxWidth()
) {
SpaceMemberIcon(
icon = state.icon,
modifier = Modifier.align(Alignment.Center),
iconSize = 72.dp
)
}
Spacer(modifier = Modifier.height(20.dp))
Text(
text = stringResource(
R.string.multiplayer_space_join_request_header,
state.newMemberName.ifEmpty { stringResource(id = R.string.untitled) },
state.spaceName.ifEmpty { stringResource(id = R.string.untitled) }
),
style = HeadlineHeading,
textAlign = TextAlign.Center,
modifier = Modifier.padding(
horizontal = 48.dp
),
color = colorResource(id = R.color.text_primary)
)
Spacer(modifier = Modifier.height(20.dp))
Buttons(
newMember = state.newMember,
buttons = state.buttons,
onAddViewerClicked = onAddViewerClicked,
onAddEditorClicked = onAddEditorClicked,
onRejectClicked = onRejectClicked,
onUpgradeClicked = onUpgradeClicked
)
Spacer(modifier = Modifier.height(16.dp))
}
is ViewState.Error.ActiveTierError -> {
ErrorState(msg = stringResource(R.string.multiplayer_space_join_request_membership_status_error))
}
is ViewState.Error.CurrentUserStatusError -> {
ErrorState(msg = stringResource(R.string.multiplayer_space_join_request_current_user_error))
}
is ViewState.Error.NewMemberError -> {
ErrorState(msg = stringResource(R.string.multiplayer_space_join_request_new_member_error))
}
is ViewState.Error.SpaceParticipantsError -> {
ErrorState(msg = stringResource(R.string.multiplayer_space_join_request_participants_error))
}
}
Spacer(modifier = Modifier.height(20.dp))
Text(
text = stringResource(
R.string.multiplayer_space_join_request_header,
state.memberName.ifEmpty { stringResource(id = R.string.untitled) },
state.spaceName.ifEmpty { stringResource(id = R.string.untitled) }
),
style = HeadlineHeading,
textAlign = TextAlign.Center,
modifier = Modifier.padding(
horizontal = 48.dp
),
color = colorResource(id = R.color.text_primary)
)
Spacer(modifier = Modifier.height(20.dp))
ButtonSecondary(
text = stringResource(R.string.multiplayer_space_add_viewer),
onClick = throttledClick(
onClick = { onAddViewerClicked() }
),
size = ButtonSize.Large,
modifier = Modifier
.padding(horizontal = 20.dp)
.fillMaxWidth(),
enabled = state.canAddAsReader
)
Spacer(modifier = Modifier.height(10.dp))
ButtonSecondary(
text = stringResource(R.string.multiplayer_space_add_editor),
onClick = throttledClick(
onClick = { onAddEditorClicked() }
),
size = ButtonSize.Large,
modifier = Modifier
.padding(horizontal = 20.dp)
.fillMaxWidth(),
enabled = state.canAddAsEditor
)
Spacer(modifier = Modifier.height(10.dp))
ButtonWarning(
text = stringResource(R.string.multiplayer_space_request_reject),
onClick = throttledClick(
onClick = { onRejectClicked() }
),
size = ButtonSize.Large,
modifier = Modifier
.padding(horizontal = 20.dp)
.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
}
}
@Composable
private fun CommentView() {
private fun ErrorState(msg: String) {
Box(
modifier = Modifier
.height(IntrinsicSize.Min)
.padding(start = 20.dp, end = 20.dp)
.background(
color = colorResource(id = R.color.shape_tertiary),
shape = RoundedCornerShape(4.dp)
)
.fillMaxWidth()
.height(346.dp),
contentAlignment = Alignment.Center
) {
Text(
text = "",
style = BodyCalloutRegular,
color = colorResource(id = R.color.text_primary),
modifier = Modifier.padding(16.dp)
)
Box(
modifier = Modifier
.fillMaxHeight()
.width(4.dp)
.background(
color = colorResource(id = R.color.glyph_active),
shape = RoundedCornerShape(
topStart = 4.dp,
bottomStart = 4.dp
)
)
text = msg,
style = BodyRegular,
color = colorResource(R.color.text_primary),
maxLines = 3
)
}
}
@Composable
private fun Buttons(
newMember: Id,
buttons: List<InviteButton>,
onAddViewerClicked: (Id) -> Unit,
onAddEditorClicked: (Id) -> Unit,
onRejectClicked: (Id) -> Unit,
onUpgradeClicked: () -> Unit,
) {
buttons.forEach {
when (it) {
InviteButton.JOIN_AS_VIEWER -> ButtonSecondary(
text = stringResource(R.string.multiplayer_space_add_viewer),
onClick = throttledClick(
onClick = { onAddViewerClicked(newMember) }
),
size = ButtonSize.Large,
modifier = Modifier
.padding(start = 20.dp, end = 20.dp, top = 8.dp)
.fillMaxWidth()
)
InviteButton.JOIN_AS_VIEWER_DISABLED -> ButtonSecondary(
text = stringResource(R.string.multiplayer_space_add_viewer),
onClick = throttledClick(
onClick = { }
),
size = ButtonSize.Large,
modifier = Modifier
.padding(start = 20.dp, end = 20.dp, top = 8.dp)
.fillMaxWidth(),
enabled = false
)
InviteButton.JOIN_AS_EDITOR -> ButtonSecondary(
text = stringResource(R.string.multiplayer_space_add_editor),
onClick = throttledClick(
onClick = { onAddEditorClicked(newMember) }
),
size = ButtonSize.Large,
modifier = Modifier
.padding(start = 20.dp, end = 20.dp, top = 8.dp)
.fillMaxWidth()
)
InviteButton.JOIN_AS_EDITOR_DISABLED -> ButtonSecondary(
text = stringResource(R.string.multiplayer_space_add_editor),
onClick = throttledClick(
onClick = { }
),
size = ButtonSize.Large,
modifier = Modifier
.padding(start = 20.dp, end = 20.dp, top = 8.dp)
.fillMaxWidth(),
enabled = false
)
InviteButton.REJECT -> ButtonWarning(
text = stringResource(R.string.multiplayer_space_request_reject),
onClick = throttledClick(
onClick = { onRejectClicked(newMember) }
),
size = ButtonSize.Large,
modifier = Modifier
.padding(start = 20.dp, end = 20.dp, top = 10.dp)
.fillMaxWidth(),
)
InviteButton.ADD_MORE_VIEWERS -> ButtonSecondary(
text = stringResource(R.string.multiplayer_space_add_more_viewer),
onClick = throttledClick(
onClick = { onUpgradeClicked() }
),
size = ButtonSize.Large,
modifier = Modifier
.padding(start = 20.dp, end = 20.dp, top = 8.dp)
.fillMaxWidth()
)
InviteButton.ADD_MORE_EDITORS -> ButtonSecondary(
text = stringResource(R.string.multiplayer_space_add_more_editor),
onClick = throttledClick(
onClick = { onUpgradeClicked() }
),
size = ButtonSize.Large,
modifier = Modifier
.padding(start = 20.dp, end = 20.dp, top = 8.dp)
.fillMaxWidth()
)
InviteButton.UPGRADE -> ButtonUpgrade(
modifier = Modifier
.padding(start = 20.dp, end = 20.dp, top = 10.dp)
.height(48.dp)
.verticalScroll(rememberScrollState()),
onClick = { onUpgradeClicked() },
text = stringResource(id = R.string.multiplayer_upgrade_button_request),
style = ButtonMedium
)
}
}
}

View file

@ -34,10 +34,9 @@ import com.anytypeio.anytype.core_ui.views.ButtonPrimary
import com.anytypeio.anytype.core_ui.views.ButtonSecondary
import com.anytypeio.anytype.core_ui.views.ButtonSize
import com.anytypeio.anytype.core_ui.views.ButtonWarningLoading
import com.anytypeio.anytype.core_ui.views.Caption1Regular
import com.anytypeio.anytype.core_ui.views.HeadlineHeading
import com.anytypeio.anytype.core_ui.views.Title1
import com.anytypeio.anytype.presentation.membership.models.MembershipStatus
import com.anytypeio.anytype.core_models.membership.MembershipStatus
@Composable
fun Toolbar(

View file

@ -1321,9 +1321,10 @@
<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>
<string name="multiplayer_share_space">Share space</string>
<string name="multiplayer_members_and_requests">Members and requests</string>
<string name="multiplayer_members_and_requests">Members</string>
<string name="multiplayer_cant_add_members">You cant add more members</string>
<string name="multiplayer_upgrade_button">✦ Upgrade</string>
<string name="multiplayer_upgrade_button_request">✦ Upgrade to add more members</string>
<string name="multiplayer_members">Members</string>
<string name="multiplayer_leave_request">Leave request</string>
<string name="multiplayer_join_request">Join request</string>
@ -1333,7 +1334,9 @@
<string name="multiplayer_can_edit">Can edit</string>
<string name="multiplayer_can_view">Can view</string>
<string name="multiplayer_space_add_viewer">Add viewer</string>
<string name="multiplayer_space_add_more_viewer">✦ Add more viewers</string>
<string name="multiplayer_space_add_editor">Add editor</string>
<string name="multiplayer_space_add_more_editor">✦ Add more editors</string>
<string name="multiplayer_space_request_reject">Reject</string>
<string name="multiplayer_space_join_request_header">%1$s requested to join %2$s space</string>
<string name="multiplayer_remove_member">Remove member</string>
@ -1637,6 +1640,10 @@ Please provide specific details of your needs here.</string>
<string name="membership_support_already_acquired">Youve already acquired a Membership plan using another Anytype account.</string>
<string name="membership_support_different_subscription">Found a subscription with a different id</string>
<string name="membership_support_more_then_one_subscription">Found more than one subscription</string>
<string name="multiplayer_space_join_request_participants_error">Space participant's error</string>
<string name="multiplayer_space_join_request_new_member_error">Request's member error</string>
<string name="multiplayer_space_join_request_current_user_error">Current user status error</string>
<string name="multiplayer_space_join_request_membership_status_error">Current membership status error</string>
<plurals name="period_years">
<item quantity="one">year</item>

View file

@ -11,13 +11,13 @@ import com.anytypeio.anytype.core_models.membership.MembershipPaymentMethod.METH
import com.anytypeio.anytype.core_models.membership.MembershipPeriodType
import com.anytypeio.anytype.core_models.membership.MembershipTierData
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.payments.constants.MembershipConstants
import com.anytypeio.anytype.payments.constants.MembershipConstants.ACTIVE_TIERS_WITH_BANNERS
import com.anytypeio.anytype.payments.constants.MembershipConstants.CO_CREATOR_ID
import com.anytypeio.anytype.payments.constants.MembershipConstants.MEMBERSHIP_CONTACT_EMAIL
import com.anytypeio.anytype.payments.constants.MembershipConstants.MEMBERSHIP_LEVEL_DETAILS
import com.anytypeio.anytype.payments.constants.MembershipConstants.PRIVACY_POLICY
import com.anytypeio.anytype.payments.constants.MembershipConstants.TERMS_OF_SERVICE
import com.anytypeio.anytype.core_models.membership.MembershipConstants
import com.anytypeio.anytype.core_models.membership.MembershipConstants.ACTIVE_TIERS_WITH_BANNERS
import com.anytypeio.anytype.core_models.membership.MembershipConstants.CO_CREATOR_ID
import com.anytypeio.anytype.core_models.membership.MembershipConstants.MEMBERSHIP_CONTACT_EMAIL
import com.anytypeio.anytype.core_models.membership.MembershipConstants.MEMBERSHIP_LEVEL_DETAILS
import com.anytypeio.anytype.core_models.membership.MembershipConstants.PRIVACY_POLICY
import com.anytypeio.anytype.core_models.membership.MembershipConstants.TERMS_OF_SERVICE
import com.anytypeio.anytype.payments.models.BillingPriceInfo
import com.anytypeio.anytype.payments.models.MembershipPurchase
import com.anytypeio.anytype.payments.models.Tier
@ -30,8 +30,8 @@ import com.anytypeio.anytype.payments.models.TierPreview
import com.anytypeio.anytype.payments.playbilling.BillingClientState
import com.anytypeio.anytype.payments.playbilling.BillingPurchaseState
import com.anytypeio.anytype.payments.viewmodel.MembershipMainState
import com.anytypeio.anytype.presentation.membership.models.MembershipStatus
import com.anytypeio.anytype.presentation.membership.models.TierId
import com.anytypeio.anytype.core_models.membership.MembershipStatus
import com.anytypeio.anytype.core_models.membership.TierId
fun MembershipStatus.toMainView(
billingClientState: BillingClientState,

View file

@ -2,7 +2,7 @@ package com.anytypeio.anytype.payments.models
import com.anytypeio.anytype.core_models.membership.MembershipErrors
import com.anytypeio.anytype.core_models.membership.MembershipPaymentMethod
import com.anytypeio.anytype.presentation.membership.models.TierId
import com.anytypeio.anytype.core_models.membership.TierId
//This is a data class that represents a tier preview view in the main Membership screen
data class TierPreview(

View file

@ -7,7 +7,6 @@ import androidx.compose.animation.fadeOut
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@ -63,7 +62,7 @@ import com.anytypeio.anytype.core_ui.views.fontRiccioneRegular
import com.anytypeio.anytype.payments.models.TierPreview
import com.anytypeio.anytype.payments.viewmodel.MembershipMainState
import com.anytypeio.anytype.payments.viewmodel.TierAction
import com.anytypeio.anytype.presentation.membership.models.TierId
import com.anytypeio.anytype.core_models.membership.TierId
@Composable
fun MainMembershipScreen(

View file

@ -38,15 +38,15 @@ import com.anytypeio.anytype.core_ui.views.ButtonSize
import com.anytypeio.anytype.core_ui.views.Caption1Regular
import com.anytypeio.anytype.core_ui.views.Relations3
import com.anytypeio.anytype.core_ui.views.fontInterSemibold
import com.anytypeio.anytype.payments.constants.MembershipConstants.BUILDER_ID
import com.anytypeio.anytype.payments.constants.MembershipConstants.CO_CREATOR_ID
import com.anytypeio.anytype.payments.constants.MembershipConstants.EXPLORER_ID
import com.anytypeio.anytype.core_models.membership.MembershipConstants.BUILDER_ID
import com.anytypeio.anytype.core_models.membership.MembershipConstants.CO_CREATOR_ID
import com.anytypeio.anytype.core_models.membership.MembershipConstants.EXPLORER_ID
import com.anytypeio.anytype.payments.models.TierPreview
import com.anytypeio.anytype.payments.models.Tier
import com.anytypeio.anytype.payments.models.TierConditionInfo
import com.anytypeio.anytype.payments.models.TierPeriod
import com.anytypeio.anytype.presentation.editor.cover.CoverColor
import com.anytypeio.anytype.presentation.membership.models.TierId
import com.anytypeio.anytype.core_models.membership.TierId
@Composable
fun TierPreviewView(

View file

@ -36,7 +36,6 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.core_models.membership.MembershipErrors
import com.anytypeio.anytype.core_models.membership.MembershipPaymentMethod
import com.anytypeio.anytype.core_ui.views.BodyCallout
import com.anytypeio.anytype.core_ui.views.ButtonPrimary
@ -45,9 +44,9 @@ import com.anytypeio.anytype.core_ui.views.ButtonSize
import com.anytypeio.anytype.core_ui.views.HeadlineTitle
import com.anytypeio.anytype.core_ui.views.Relations2
import com.anytypeio.anytype.payments.R
import com.anytypeio.anytype.payments.constants.MembershipConstants.EXPLORER_ID
import com.anytypeio.anytype.payments.constants.MembershipConstants.PRIVACY_POLICY
import com.anytypeio.anytype.payments.constants.MembershipConstants.TERMS_OF_SERVICE
import com.anytypeio.anytype.core_models.membership.MembershipConstants.EXPLORER_ID
import com.anytypeio.anytype.core_models.membership.MembershipConstants.PRIVACY_POLICY
import com.anytypeio.anytype.core_models.membership.MembershipConstants.TERMS_OF_SERVICE
import com.anytypeio.anytype.payments.models.TierAnyName
import com.anytypeio.anytype.payments.models.TierButton
import com.anytypeio.anytype.payments.models.TierConditionInfo
@ -56,7 +55,7 @@ import com.anytypeio.anytype.payments.models.TierPeriod
import com.anytypeio.anytype.payments.models.Tier
import com.anytypeio.anytype.payments.viewmodel.MembershipTierState
import com.anytypeio.anytype.payments.viewmodel.TierAction
import com.anytypeio.anytype.presentation.membership.models.TierId
import com.anytypeio.anytype.core_models.membership.TierId
import timber.log.Timber

View file

@ -42,7 +42,7 @@ import com.anytypeio.anytype.payments.models.TierEmail
import com.anytypeio.anytype.payments.models.TierPeriod
import com.anytypeio.anytype.payments.models.Tier
import com.anytypeio.anytype.payments.viewmodel.WelcomeState
import com.anytypeio.anytype.presentation.membership.models.TierId
import com.anytypeio.anytype.core_models.membership.TierId
@OptIn(ExperimentalMaterial3Api::class)
@Composable

View file

@ -4,7 +4,7 @@ import androidx.annotation.StringRes
import com.anytypeio.anytype.core_models.membership.MembershipErrors
import com.anytypeio.anytype.payments.models.TierPreview
import com.anytypeio.anytype.payments.models.Tier
import com.anytypeio.anytype.presentation.membership.models.TierId
import com.anytypeio.anytype.core_models.membership.TierId
sealed class MembershipMainState {

View file

@ -20,8 +20,8 @@ import com.anytypeio.anytype.domain.payments.GetMembershipPaymentUrl
import com.anytypeio.anytype.domain.payments.IsMembershipNameValid
import com.anytypeio.anytype.domain.payments.SetMembershipEmail
import com.anytypeio.anytype.domain.payments.VerifyMembershipEmailCode
import com.anytypeio.anytype.payments.constants.MembershipConstants.EXPLORER_ID
import com.anytypeio.anytype.payments.constants.MembershipConstants.MEMBERSHIP_NAME_MIN_LENGTH
import com.anytypeio.anytype.core_models.membership.MembershipConstants.EXPLORER_ID
import com.anytypeio.anytype.core_models.membership.MembershipConstants.MEMBERSHIP_NAME_MIN_LENGTH
import com.anytypeio.anytype.payments.mapping.toMainView
import com.anytypeio.anytype.payments.models.MembershipPurchase
import com.anytypeio.anytype.payments.models.TierAnyName
@ -34,8 +34,8 @@ import com.anytypeio.anytype.payments.playbilling.BillingPurchaseState
import com.anytypeio.anytype.presentation.extension.sendAnalyticsMembershipClickEvent
import com.anytypeio.anytype.presentation.extension.sendAnalyticsMembershipPurchaseEvent
import com.anytypeio.anytype.presentation.extension.sendAnalyticsMembershipScreenEvent
import com.anytypeio.anytype.presentation.membership.models.MembershipStatus
import com.anytypeio.anytype.presentation.membership.models.TierId
import com.anytypeio.anytype.core_models.membership.MembershipStatus
import com.anytypeio.anytype.core_models.membership.TierId
import com.anytypeio.anytype.presentation.membership.provider.MembershipProvider
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.Job

View file

@ -6,7 +6,7 @@ import com.anytypeio.anytype.core_models.membership.Membership
import com.anytypeio.anytype.core_models.membership.MembershipPaymentMethod
import com.anytypeio.anytype.core_models.membership.MembershipPeriodType
import com.anytypeio.anytype.core_models.membership.MembershipTierData
import com.anytypeio.anytype.payments.constants.MembershipConstants
import com.anytypeio.anytype.core_models.membership.MembershipConstants
import com.anytypeio.anytype.payments.models.BillingPriceInfo
import com.anytypeio.anytype.payments.models.PeriodDescription
import com.anytypeio.anytype.payments.models.PeriodUnit
@ -18,8 +18,8 @@ import com.anytypeio.anytype.payments.models.TierPreview
import com.anytypeio.anytype.payments.playbilling.BillingClientState
import com.anytypeio.anytype.payments.viewmodel.MembershipMainState
import com.anytypeio.anytype.payments.viewmodel.MembershipTierState
import com.anytypeio.anytype.presentation.membership.models.MembershipStatus
import com.anytypeio.anytype.presentation.membership.models.TierId
import com.anytypeio.anytype.core_models.membership.MembershipStatus
import com.anytypeio.anytype.core_models.membership.TierId
import junit.framework.TestCase
import kotlin.test.assertIs
import kotlinx.coroutines.test.runTest

View file

@ -15,7 +15,7 @@ import com.anytypeio.anytype.domain.payments.GetMembershipPaymentUrl
import com.anytypeio.anytype.domain.payments.IsMembershipNameValid
import com.anytypeio.anytype.domain.payments.SetMembershipEmail
import com.anytypeio.anytype.domain.payments.VerifyMembershipEmailCode
import com.anytypeio.anytype.payments.constants.MembershipConstants
import com.anytypeio.anytype.core_models.membership.MembershipConstants
import com.anytypeio.anytype.payments.models.TierAnyName
import com.anytypeio.anytype.payments.models.TierButton
import com.anytypeio.anytype.payments.models.TierConditionInfo
@ -25,8 +25,8 @@ import com.anytypeio.anytype.payments.playbilling.BillingClientLifecycle
import com.anytypeio.anytype.payments.playbilling.BillingClientState
import com.anytypeio.anytype.payments.playbilling.BillingPurchaseState
import com.anytypeio.anytype.payments.viewmodel.MembershipViewModel
import com.anytypeio.anytype.presentation.membership.models.MembershipStatus
import com.anytypeio.anytype.presentation.membership.models.TierId
import com.anytypeio.anytype.core_models.membership.MembershipStatus
import com.anytypeio.anytype.core_models.membership.TierId
import com.anytypeio.anytype.presentation.membership.provider.MembershipProvider
import com.anytypeio.anytype.test_utils.MockDataFactory
import junit.framework.TestCase.assertEquals

View file

@ -3,17 +3,17 @@ package com.anytypeio.anytype.payments
import app.cash.turbine.turbineScope
import com.anytypeio.anytype.core_models.membership.Membership
import com.anytypeio.anytype.core_models.membership.MembershipPaymentMethod
import com.anytypeio.anytype.payments.constants.MembershipConstants.ACTIVE_TIERS_WITH_BANNERS
import com.anytypeio.anytype.payments.constants.MembershipConstants.BUILDER_ID
import com.anytypeio.anytype.payments.constants.MembershipConstants.CO_CREATOR_ID
import com.anytypeio.anytype.payments.constants.MembershipConstants.EXPLORER_ID
import com.anytypeio.anytype.core_models.membership.MembershipConstants.ACTIVE_TIERS_WITH_BANNERS
import com.anytypeio.anytype.core_models.membership.MembershipConstants.BUILDER_ID
import com.anytypeio.anytype.core_models.membership.MembershipConstants.CO_CREATOR_ID
import com.anytypeio.anytype.core_models.membership.MembershipConstants.EXPLORER_ID
import com.anytypeio.anytype.payments.viewmodel.MembershipMainState
import com.anytypeio.anytype.payments.viewmodel.MembershipTierState
import com.anytypeio.anytype.payments.viewmodel.MembershipEmailCodeState
import com.anytypeio.anytype.payments.viewmodel.MembershipErrorState
import com.anytypeio.anytype.payments.viewmodel.WelcomeState
import com.anytypeio.anytype.presentation.membership.models.MembershipStatus
import com.anytypeio.anytype.presentation.membership.models.TierId
import com.anytypeio.anytype.core_models.membership.MembershipStatus
import com.anytypeio.anytype.core_models.membership.TierId
import kotlin.test.assertFalse
import kotlin.test.assertIs
import kotlin.test.assertTrue

View file

@ -5,7 +5,7 @@ import com.anytypeio.anytype.core_models.membership.Membership
import com.anytypeio.anytype.core_models.membership.MembershipPaymentMethod
import com.anytypeio.anytype.core_models.membership.MembershipPeriodType
import com.anytypeio.anytype.core_models.membership.MembershipTierData
import com.anytypeio.anytype.payments.constants.MembershipConstants
import com.anytypeio.anytype.core_models.membership.MembershipConstants
import com.anytypeio.anytype.payments.models.TierAnyName
import com.anytypeio.anytype.payments.models.TierButton
import com.anytypeio.anytype.payments.models.TierConditionInfo
@ -15,8 +15,8 @@ import com.anytypeio.anytype.payments.models.TierPreview
import com.anytypeio.anytype.payments.playbilling.BillingClientState
import com.anytypeio.anytype.payments.viewmodel.MembershipMainState
import com.anytypeio.anytype.payments.viewmodel.MembershipTierState
import com.anytypeio.anytype.presentation.membership.models.MembershipStatus
import com.anytypeio.anytype.presentation.membership.models.TierId
import com.anytypeio.anytype.core_models.membership.MembershipStatus
import com.anytypeio.anytype.core_models.membership.TierId
import junit.framework.TestCase
import kotlin.test.assertIs
import kotlinx.coroutines.test.runTest

View file

@ -5,7 +5,7 @@ import com.anytypeio.anytype.core_models.membership.Membership
import com.anytypeio.anytype.core_models.membership.MembershipPaymentMethod
import com.anytypeio.anytype.core_models.membership.MembershipPeriodType
import com.anytypeio.anytype.core_models.membership.MembershipTierData
import com.anytypeio.anytype.payments.constants.MembershipConstants
import com.anytypeio.anytype.core_models.membership.MembershipConstants
import com.anytypeio.anytype.payments.models.TierAnyName
import com.anytypeio.anytype.payments.models.TierButton
import com.anytypeio.anytype.payments.models.TierConditionInfo
@ -15,8 +15,8 @@ import com.anytypeio.anytype.payments.models.TierPreview
import com.anytypeio.anytype.payments.playbilling.BillingClientState
import com.anytypeio.anytype.payments.viewmodel.MembershipMainState
import com.anytypeio.anytype.payments.viewmodel.MembershipTierState
import com.anytypeio.anytype.presentation.membership.models.MembershipStatus
import com.anytypeio.anytype.presentation.membership.models.TierId
import com.anytypeio.anytype.core_models.membership.MembershipStatus
import com.anytypeio.anytype.core_models.membership.TierId
import junit.framework.TestCase
import kotlin.test.assertIs
import kotlinx.coroutines.test.runTest

View file

@ -1,14 +1,12 @@
package com.anytypeio.anytype.payments
import app.cash.turbine.turbineScope
import com.android.billingclient.api.AccountIdentifiers
import com.android.billingclient.api.ProductDetails
import com.android.billingclient.api.Purchase
import com.anytypeio.anytype.core_models.membership.Membership
import com.anytypeio.anytype.core_models.membership.MembershipPaymentMethod
import com.anytypeio.anytype.core_models.membership.MembershipPeriodType
import com.anytypeio.anytype.core_models.membership.MembershipTierData
import com.anytypeio.anytype.payments.constants.MembershipConstants
import com.anytypeio.anytype.core_models.membership.MembershipConstants
import com.anytypeio.anytype.payments.models.BillingPriceInfo
import com.anytypeio.anytype.payments.models.MembershipPurchase
import com.anytypeio.anytype.payments.models.PeriodDescription
@ -22,9 +20,8 @@ import com.anytypeio.anytype.payments.playbilling.BillingClientState
import com.anytypeio.anytype.payments.playbilling.BillingPurchaseState
import com.anytypeio.anytype.payments.viewmodel.MembershipMainState
import com.anytypeio.anytype.payments.viewmodel.MembershipTierState
import com.anytypeio.anytype.presentation.membership.models.MembershipStatus
import com.anytypeio.anytype.presentation.membership.models.TierId
import java.lang.reflect.Member
import com.anytypeio.anytype.core_models.membership.MembershipStatus
import com.anytypeio.anytype.core_models.membership.TierId
import kotlin.test.assertIs
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flow
@ -35,7 +32,6 @@ import org.junit.Test
import org.mockito.Mockito
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.stub
import org.mockito.kotlin.whenever
class TierActiveWithDifferentSubIdTest : MembershipTestsSetup() {

View file

@ -5,7 +5,7 @@ import com.anytypeio.anytype.core_models.membership.Membership
import com.anytypeio.anytype.core_models.membership.MembershipPaymentMethod
import com.anytypeio.anytype.core_models.membership.MembershipPeriodType
import com.anytypeio.anytype.core_models.membership.MembershipTierData
import com.anytypeio.anytype.payments.constants.MembershipConstants
import com.anytypeio.anytype.core_models.membership.MembershipConstants
import com.anytypeio.anytype.payments.models.TierAnyName
import com.anytypeio.anytype.payments.models.TierButton
import com.anytypeio.anytype.payments.models.TierConditionInfo
@ -15,8 +15,8 @@ import com.anytypeio.anytype.payments.models.TierPreview
import com.anytypeio.anytype.payments.playbilling.BillingClientState
import com.anytypeio.anytype.payments.viewmodel.MembershipMainState
import com.anytypeio.anytype.payments.viewmodel.MembershipTierState
import com.anytypeio.anytype.presentation.membership.models.MembershipStatus
import com.anytypeio.anytype.presentation.membership.models.TierId
import com.anytypeio.anytype.core_models.membership.MembershipStatus
import com.anytypeio.anytype.core_models.membership.TierId
import junit.framework.TestCase
import kotlin.test.assertIs
import kotlinx.coroutines.test.runTest

View file

@ -1,12 +1,11 @@
package com.anytypeio.anytype.payments
import app.cash.turbine.turbineScope
import com.android.billingclient.api.Purchase
import com.anytypeio.anytype.core_models.membership.Membership
import com.anytypeio.anytype.core_models.membership.MembershipPaymentMethod
import com.anytypeio.anytype.core_models.membership.MembershipPeriodType
import com.anytypeio.anytype.core_models.membership.MembershipTierData
import com.anytypeio.anytype.payments.constants.MembershipConstants
import com.anytypeio.anytype.core_models.membership.MembershipConstants
import com.anytypeio.anytype.payments.models.MembershipPurchase
import com.anytypeio.anytype.payments.models.TierAnyName
import com.anytypeio.anytype.payments.models.TierButton
@ -17,14 +16,13 @@ import com.anytypeio.anytype.payments.models.TierPreview
import com.anytypeio.anytype.payments.playbilling.BillingPurchaseState
import com.anytypeio.anytype.payments.viewmodel.MembershipMainState
import com.anytypeio.anytype.payments.viewmodel.MembershipTierState
import com.anytypeio.anytype.presentation.membership.models.MembershipStatus
import com.anytypeio.anytype.presentation.membership.models.TierId
import com.anytypeio.anytype.core_models.membership.MembershipStatus
import com.anytypeio.anytype.core_models.membership.TierId
import junit.framework.TestCase
import kotlin.test.assertIs
import kotlinx.coroutines.test.runTest
import net.bytebuddy.utility.RandomString
import org.junit.Test
import org.mockito.Mockito
/**
* Tier - active and non free | with androidId | purchased through Android

View file

@ -5,7 +5,7 @@ import com.anytypeio.anytype.core_models.membership.Membership
import com.anytypeio.anytype.core_models.membership.MembershipPaymentMethod
import com.anytypeio.anytype.core_models.membership.MembershipPeriodType
import com.anytypeio.anytype.core_models.membership.MembershipTierData
import com.anytypeio.anytype.payments.constants.MembershipConstants
import com.anytypeio.anytype.core_models.membership.MembershipConstants
import com.anytypeio.anytype.payments.models.MembershipPurchase
import com.anytypeio.anytype.payments.models.TierAnyName
import com.anytypeio.anytype.payments.models.TierButton
@ -17,8 +17,8 @@ import com.anytypeio.anytype.payments.playbilling.BillingClientState
import com.anytypeio.anytype.payments.playbilling.BillingPurchaseState
import com.anytypeio.anytype.payments.viewmodel.MembershipMainState
import com.anytypeio.anytype.payments.viewmodel.MembershipTierState
import com.anytypeio.anytype.presentation.membership.models.MembershipStatus
import com.anytypeio.anytype.presentation.membership.models.TierId
import com.anytypeio.anytype.core_models.membership.MembershipStatus
import com.anytypeio.anytype.core_models.membership.TierId
import junit.framework.TestCase
import kotlin.test.assertIs
import kotlinx.coroutines.test.runTest

View file

@ -6,7 +6,7 @@ import com.anytypeio.anytype.core_models.membership.Membership
import com.anytypeio.anytype.core_models.membership.MembershipPaymentMethod
import com.anytypeio.anytype.core_models.membership.MembershipPeriodType
import com.anytypeio.anytype.core_models.membership.MembershipTierData
import com.anytypeio.anytype.payments.constants.MembershipConstants
import com.anytypeio.anytype.core_models.membership.MembershipConstants
import com.anytypeio.anytype.payments.models.BillingPriceInfo
import com.anytypeio.anytype.payments.models.PeriodDescription
import com.anytypeio.anytype.payments.models.PeriodUnit
@ -18,8 +18,8 @@ import com.anytypeio.anytype.payments.models.TierPreview
import com.anytypeio.anytype.payments.playbilling.BillingClientState
import com.anytypeio.anytype.payments.viewmodel.MembershipMainState
import com.anytypeio.anytype.payments.viewmodel.MembershipTierState
import com.anytypeio.anytype.presentation.membership.models.MembershipStatus
import com.anytypeio.anytype.presentation.membership.models.TierId
import com.anytypeio.anytype.core_models.membership.MembershipStatus
import com.anytypeio.anytype.core_models.membership.TierId
import junit.framework.TestCase.assertEquals
import kotlin.test.assertIs
import kotlinx.coroutines.test.runTest

View file

@ -2,13 +2,12 @@ package com.anytypeio.anytype.payments
import app.cash.turbine.turbineScope
import com.android.billingclient.api.ProductDetails
import com.android.billingclient.api.Purchase
import com.anytypeio.anytype.core_models.membership.Membership
import com.anytypeio.anytype.core_models.membership.MembershipPaymentMethod
import com.anytypeio.anytype.core_models.membership.MembershipPeriodType
import com.anytypeio.anytype.core_models.membership.MembershipTierData
import com.anytypeio.anytype.payments.constants.MembershipConstants.BUILDER_ID
import com.anytypeio.anytype.payments.constants.MembershipConstants.EXPLORER_ID
import com.anytypeio.anytype.core_models.membership.MembershipConstants.BUILDER_ID
import com.anytypeio.anytype.core_models.membership.MembershipConstants.EXPLORER_ID
import com.anytypeio.anytype.payments.models.BillingPriceInfo
import com.anytypeio.anytype.payments.models.MembershipPurchase
import com.anytypeio.anytype.payments.models.PeriodDescription
@ -22,8 +21,8 @@ import com.anytypeio.anytype.payments.playbilling.BillingClientState
import com.anytypeio.anytype.payments.playbilling.BillingPurchaseState
import com.anytypeio.anytype.payments.viewmodel.MembershipMainState
import com.anytypeio.anytype.payments.viewmodel.MembershipTierState
import com.anytypeio.anytype.presentation.membership.models.MembershipStatus
import com.anytypeio.anytype.presentation.membership.models.TierId
import com.anytypeio.anytype.core_models.membership.MembershipStatus
import com.anytypeio.anytype.core_models.membership.TierId
import kotlin.test.assertEquals
import kotlin.test.assertIs
import kotlinx.coroutines.delay

View file

@ -4,13 +4,13 @@ import app.cash.turbine.turbineScope
import com.anytypeio.anytype.core_models.membership.Membership
import com.anytypeio.anytype.core_models.membership.MembershipPaymentMethod
import com.anytypeio.anytype.core_models.membership.MembershipPeriodType
import com.anytypeio.anytype.payments.constants.MembershipConstants
import com.anytypeio.anytype.core_models.membership.MembershipConstants
import com.anytypeio.anytype.payments.models.TierConditionInfo
import com.anytypeio.anytype.payments.models.TierPeriod
import com.anytypeio.anytype.payments.playbilling.BillingPurchaseState
import com.anytypeio.anytype.payments.viewmodel.MembershipMainState
import com.anytypeio.anytype.presentation.membership.models.MembershipStatus
import com.anytypeio.anytype.presentation.membership.models.TierId
import com.anytypeio.anytype.core_models.membership.MembershipStatus
import com.anytypeio.anytype.core_models.membership.TierId
import kotlin.test.assertIs
import kotlinx.coroutines.test.runTest
import net.bytebuddy.utility.RandomString

View file

@ -46,6 +46,7 @@ import com.anytypeio.anytype.core_models.TextStyle
import com.anytypeio.anytype.core_models.ThemeMode
import com.anytypeio.anytype.core_models.WidgetLayout
import com.anytypeio.anytype.core_models.ext.mapToObjectWrapperType
import com.anytypeio.anytype.core_models.multiplayer.SpaceMemberPermissions
import com.anytypeio.anytype.core_utils.ext.Mimetype
import com.anytypeio.anytype.domain.config.ConfigStorage
import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate
@ -2054,4 +2055,22 @@ fun CoroutineScope.sendAnalyticsMembershipPurchaseEvent(
props = Props(mapOf(EventsPropertiesKey.name to tier))
)
}
//endregion
//endregion
suspend fun Analytics.sendAnalyticsApproveInvite(
permissions: SpaceMemberPermissions
) {
val type = when (permissions) {
SpaceMemberPermissions.READER -> EventsDictionary.SharingInviteRequest.reader
SpaceMemberPermissions.WRITER -> EventsDictionary.SharingInviteRequest.writer
else -> ""
}
sendEvent(
eventName = EventsDictionary.approveInviteRequest,
props = Props(
mapOf(
EventsPropertiesKey.type to type
)
)
)
}

View file

@ -9,8 +9,8 @@ import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.misc.DateProvider
import com.anytypeio.anytype.domain.misc.LocaleProvider
import com.anytypeio.anytype.domain.workspace.MembershipChannel
import com.anytypeio.anytype.presentation.membership.models.MembershipStatus
import com.anytypeio.anytype.presentation.membership.models.TierId
import com.anytypeio.anytype.core_models.membership.MembershipStatus
import com.anytypeio.anytype.core_models.membership.TierId
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch

View file

@ -127,7 +127,9 @@ class ShareSpaceViewModel(
)
}.collect { (spaceResponse, membersResponse, isCurrentUserOwner) ->
val spaceView = spaceResponse.toSpaceView()
val spaceMembers = membersResponse.toSpaceMembers()
val spaceMembers
= membersResponse.toSpaceMembers()
.sortedByDescending { it.status == ParticipantStatus.JOINING || it.status == ParticipantStatus.REMOVING}
spaceAccessType.value = spaceView?.spaceAccessType
setShareLinkViewState(spaceView, isCurrentUserOwner)
members.value = spaceMembers.toView(

View file

@ -9,13 +9,11 @@ import com.anytypeio.anytype.analytics.base.EventsDictionary.rejectInviteRequest
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.Config
import com.anytypeio.anytype.core_models.DVFilter
import com.anytypeio.anytype.core_models.DVFilterCondition
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.core_models.multiplayer.ParticipantStatus
import com.anytypeio.anytype.core_models.ext.isPossibleToUpgradeNumberOfSpaceMembers
import com.anytypeio.anytype.core_models.membership.MembershipConstants.BUILDER_ID
import com.anytypeio.anytype.core_models.membership.MembershipConstants.EXPLORER_ID
import com.anytypeio.anytype.core_models.multiplayer.SpaceMemberPermissions
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.core_utils.ext.msg
@ -23,327 +21,471 @@ import com.anytypeio.anytype.domain.base.fold
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.multiplayer.ApproveJoinSpaceRequest
import com.anytypeio.anytype.domain.multiplayer.DeclineSpaceJoinRequest
import com.anytypeio.anytype.domain.multiplayer.SpaceViewSubscriptionContainer
import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider
import com.anytypeio.anytype.domain.`object`.canAddReaders
import com.anytypeio.anytype.domain.`object`.canAddWriters
import com.anytypeio.anytype.domain.search.SearchObjects
import com.anytypeio.anytype.domain.workspace.SpaceManager
import com.anytypeio.anytype.presentation.common.BaseViewModel
import com.anytypeio.anytype.presentation.extension.sendAnalyticsApproveInvite
import com.anytypeio.anytype.core_models.membership.TierId
import com.anytypeio.anytype.presentation.membership.provider.MembershipProvider
import com.anytypeio.anytype.presentation.objects.SpaceMemberIconView
import com.anytypeio.anytype.presentation.objects.toSpaceMembers
import com.anytypeio.anytype.presentation.search.ObjectSearchConstants
import com.anytypeio.anytype.presentation.search.ObjectSearchConstants.filterParticipants
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.launch
import timber.log.Timber
class SpaceJoinRequestViewModel(
private val params: Params,
private val vmParams: VmParams,
private val approveJoinSpaceRequest: ApproveJoinSpaceRequest,
private val declineSpaceJoinRequest: DeclineSpaceJoinRequest,
private val searchObjects: SearchObjects,
private val spaceManager: SpaceManager,
private val urlBuilder: UrlBuilder,
private val analytics: Analytics,
private val userPermissionProvider: UserPermissionProvider
): BaseViewModel() {
private val userPermissionProvider: UserPermissionProvider,
private val spaceViewSubscriptionContainer: SpaceViewSubscriptionContainer,
private val membershipProvider: MembershipProvider
) : BaseViewModel() {
val isDismissed = MutableStateFlow(false)
private val _isCurrentUserOwner = MutableStateFlow(false)
private val _spaceMembers = MutableStateFlow<SpaceMembersState>(SpaceMembersState.Init)
private val _newMember = MutableStateFlow<NewMemberState>(NewMemberState.Init)
private val _viewState = MutableStateFlow<ViewState>(ViewState.Init)
private val _activeTier = MutableStateFlow<ActiveTierState>(ActiveTierState.Init)
val viewState: StateFlow<ViewState> = _viewState
private val state = MutableStateFlow<State>(State.Init)
val viewState = MutableStateFlow<ViewState>(ViewState.Init)
private val _commands = MutableSharedFlow<Command>(replay = 0)
val commands: SharedFlow<Command> = _commands
init {
proceedWithUserPermissions()
viewModelScope.launch {
val config = spaceManager.getConfig()
if (config != null && config.space == params.space.id) {
searchObjects(
SearchObjects.Params(
sorts = emptyList(),
filters = buildList {
add(
DVFilter(
relation = Relations.IS_ARCHIVED,
condition = DVFilterCondition.NOT_EQUAL,
value = true
)
)
add(
DVFilter(
relation = Relations.IS_DELETED,
condition = DVFilterCondition.NOT_EQUAL,
value = true
)
)
add(
DVFilter(
relation = Relations.ID,
condition = DVFilterCondition.IN,
value = listOf(config.spaceView, params.member)
)
)
},
limit = 2,
keys = listOf(
Relations.ID,
Relations.SPACE_ID,
Relations.TARGET_SPACE_ID,
Relations.IDENTITY,
Relations.ICON_IMAGE,
Relations.NAME
)
)
).process(
failure = { e ->
Timber.e(e, "Error while fetching space data and member data").also {
sendToast(e.msg())
}
},
success = { wrappers ->
val spaceView = wrappers.firstOrNull { it.id == config.spaceView }
val member = wrappers.firstOrNull { it.id == params.member }
if (spaceView != null && member != null) {
state.value = State.Success(
member = ObjectWrapper.SpaceMember(member.map),
spaceView = ObjectWrapper.SpaceView(spaceView.map),
participants = emptyList()
)
getMembers(config)
} else {
state.value = State.Error
}
}
combine(
_isCurrentUserOwner,
spaceViewSubscriptionContainer.observe(vmParams.space),
_activeTier.filterIsInstance<ActiveTierState.Success>(),
_spaceMembers.filterIsInstance<SpaceMembersState.Success>(),
_newMember.filterIsInstance<NewMemberState.Success>()
) { isCurrentUserOwner, spaceView, tierState, spaceMembersState, newMemberState ->
Result(
isCurrentUserOwner = isCurrentUserOwner,
spaceView = spaceView,
tierId = tierState.tierId,
spaceMembers = spaceMembersState.spaceMembers,
newMember = newMemberState.newMember
)
}.collect { result ->
proceedWithState(
tierId = result.tierId,
spaceView = result.spaceView,
spaceMembers = result.spaceMembers,
newMember = result.newMember,
isCurrentUserOwner = result.isCurrentUserOwner
)
} else {
state.value = State.Error
}
}
sendAnalyticsInviteScreen()
proceedWithUserPermissions(space = vmParams.space)
proceedWithSpaceMembers(space = vmParams.space)
proceedGettingNewMember()
proceedWithGettingActiveTier()
}
private fun proceedWithGettingActiveTier() {
viewModelScope.launch {
state.combine(_isCurrentUserOwner) { state, isCurrentUserOwner ->
state to isCurrentUserOwner
}.collect { (curr, isCurrentUserOwner) ->
viewState.value = when (curr) {
is State.Error -> ViewState.Error
is State.Init -> ViewState.Init
is State.Success -> ViewState.Success(
memberName = curr.member.name.orEmpty(),
spaceName = curr.spaceView.name.orEmpty(),
icon = SpaceMemberIconView.icon(
obj = curr.member,
urlBuilder = urlBuilder
),
canAddAsReader = curr.spaceView.canAddReaders(isCurrentUserOwner, curr.participants),
canAddAsEditor = curr.spaceView.canAddWriters(isCurrentUserOwner, curr.participants)
)
membershipProvider.activeTier()
.catch { e ->
Timber.e(e, "Error while fetching active tier")
_viewState.value = ViewState.Error.ActiveTierError(e.msg())
}
.collect { tierId ->
_activeTier.value = ActiveTierState.Success(tierId)
}
}
}
}
private fun sendAnalyticsInviteScreen() {
viewModelScope.launch {
analytics.sendEvent(
eventName = EventsDictionary.screenInviteConfirm,
props = Props(
mapOf(EventsPropertiesKey.route to params.route)
mapOf(EventsPropertiesKey.route to vmParams.route)
)
)
}
}
private fun proceedWithUserPermissions() {
private fun proceedWithState(
tierId: TierId,
spaceView: ObjectWrapper.SpaceView,
spaceMembers: List<ObjectWrapper.SpaceMember>,
newMember: ObjectWrapper.SpaceMember,
isCurrentUserOwner: Boolean
) {
Timber.d("proceedWithState, tierId: $tierId, spaceView: $spaceView, spaceMembers: $spaceMembers, newMember: $newMember, isCurrentUserOwner: $isCurrentUserOwner")
val state = when (tierId.value) {
EXPLORER_ID -> createExplorerState(
spaceView = spaceView,
spaceMembers = spaceMembers,
newMember = newMember,
isCurrentUserOwner = isCurrentUserOwner
)
BUILDER_ID -> createBuilderState(
spaceView = spaceView,
spaceMembers = spaceMembers,
newMember = newMember,
isCurrentUserOwner = isCurrentUserOwner
)
else -> createOtherState(
spaceView = spaceView,
spaceMembers = spaceMembers,
newMember = newMember,
isCurrentUserOwner = isCurrentUserOwner
)
}
_viewState.value = state
}
private fun createExplorerState(
spaceView: ObjectWrapper.SpaceView,
spaceMembers: List<ObjectWrapper.SpaceMember>,
newMember: ObjectWrapper.SpaceMember,
isCurrentUserOwner: Boolean
): ViewState {
val canAddReaders = spaceView.canAddReaders(isCurrentUserOwner, spaceMembers)
val canAddWriters = spaceView.canAddWriters(isCurrentUserOwner, spaceMembers)
return when {
!canAddReaders && !canAddWriters ->
createViewStateWithButtons(
spaceView = spaceView,
newMember = newMember,
buttons = listOf(InviteButton.UPGRADE, InviteButton.REJECT)
)
else -> createViewStateWithButtons(
spaceView = spaceView,
newMember = newMember,
buttons = buildList {
if (!canAddReaders) add(InviteButton.JOIN_AS_VIEWER_DISABLED)
else add(InviteButton.JOIN_AS_VIEWER)
if (!canAddWriters) add(InviteButton.JOIN_AS_EDITOR_DISABLED)
else add(InviteButton.JOIN_AS_EDITOR)
add(InviteButton.REJECT)
}
)
}
}
private fun createBuilderState(
spaceView: ObjectWrapper.SpaceView,
spaceMembers: List<ObjectWrapper.SpaceMember>,
newMember: ObjectWrapper.SpaceMember,
isCurrentUserOwner: Boolean
): ViewState {
val canAddReaders = spaceView.canAddReaders(isCurrentUserOwner, spaceMembers)
val canAddWriters = spaceView.canAddWriters(isCurrentUserOwner, spaceMembers)
return when {
!canAddReaders && !canAddWriters ->
createViewStateWithButtons(
spaceView = spaceView,
newMember = newMember,
buttons = listOf(
InviteButton.ADD_MORE_VIEWERS,
InviteButton.ADD_MORE_EDITORS,
InviteButton.REJECT
)
)
else -> createViewStateWithButtons(
spaceView = spaceView,
newMember = newMember,
buttons = buildList {
if (!canAddReaders) add(InviteButton.ADD_MORE_VIEWERS) else add(InviteButton.JOIN_AS_VIEWER)
if (!canAddWriters) add(InviteButton.ADD_MORE_EDITORS) else add(InviteButton.JOIN_AS_EDITOR)
add(InviteButton.REJECT)
}
)
}
}
private fun createOtherState(
spaceView: ObjectWrapper.SpaceView,
spaceMembers: List<ObjectWrapper.SpaceMember>,
newMember: ObjectWrapper.SpaceMember,
isCurrentUserOwner: Boolean
): ViewState {
val canAddReaders = spaceView.canAddReaders(isCurrentUserOwner, spaceMembers)
val canAddWriters = spaceView.canAddWriters(isCurrentUserOwner, spaceMembers)
return when {
!canAddReaders && !canAddWriters -> createViewStateWithButtons(
spaceView = spaceView,
newMember = newMember,
buttons = listOf(
InviteButton.ADD_MORE_VIEWERS,
InviteButton.ADD_MORE_EDITORS,
InviteButton.REJECT
)
)
else -> createViewStateWithButtons(
spaceView = spaceView,
newMember = newMember,
buttons = buildList {
if (!canAddReaders) add(InviteButton.ADD_MORE_VIEWERS) else add(InviteButton.JOIN_AS_VIEWER)
if (!canAddWriters) add(InviteButton.ADD_MORE_EDITORS) else add(InviteButton.JOIN_AS_EDITOR)
add(InviteButton.REJECT)
}
)
}
}
private fun createViewStateWithButtons(
spaceView: ObjectWrapper.SpaceView,
newMember: ObjectWrapper.SpaceMember,
buttons: List<InviteButton>
): ViewState.Success {
return ViewState.Success(
spaceName = spaceView.name.orEmpty(),
newMember = newMember.identity,
newMemberName = newMember.name.orEmpty(),
icon = SpaceMemberIconView.icon(
obj = newMember,
urlBuilder = urlBuilder
),
buttons = buttons
)
}
private fun proceedWithUserPermissions(space: SpaceId) {
viewModelScope.launch {
userPermissionProvider
.observe(space = params.space)
.observe(space = space)
.catch {
Timber.e(it, "Error while fetching user permissions")
_viewState.value = ViewState.Error.CurrentUserStatusError(it.msg())
}
.collect { permission ->
_isCurrentUserOwner.value = permission == SpaceMemberPermissions.OWNER
}
}
}
private suspend fun getMembers(config: Config) {
private fun proceedWithSpaceMembers(space: SpaceId) {
val searchMembersParams = SearchObjects.Params(
filters = filterParticipants(
spaces = listOf(config.spaceView)
spaces = listOf(space.id)
),
keys = ObjectSearchConstants.spaceMemberKeys
)
searchObjects(searchMembersParams).proceed(
failure = { Timber.e(it, "Error while fetching participants") },
success = { result ->
val currentState = state.value
if (currentState is State.Success) {
state.value = currentState.copy(
participants = result
.map { ObjectWrapper.SpaceMember(it.map) }
.filter { it.status == ParticipantStatus.ACTIVE
&& it.permissions != SpaceMemberPermissions.NO_PERMISSIONS
}
)
}
}
)
}
fun onRejectRequestClicked() {
viewModelScope.launch {
when(val curr = state.value) {
is State.Error -> {
// TODO send toast
searchObjects(searchMembersParams).process(
failure = {
Timber.e(it, "Error while fetching participants")
_viewState.value = ViewState.Error.SpaceParticipantsError(it.msg())
},
success = { result ->
Timber.d("proceedWithSpaceMembers, success: $result")
val spaceMembers = result.toSpaceMembers()
_spaceMembers.value = SpaceMembersState.Success(spaceMembers)
}
is State.Init -> {
// Do nothing
}
is State.Success -> {
declineSpaceJoinRequest.async(
DeclineSpaceJoinRequest.Params(
space = params.space,
identity = curr.member.identity
)
).fold(
onSuccess = {
analytics.sendEvent(eventName = rejectInviteRequest)
isDismissed.value = true
},
onFailure = { e ->
Timber.e(e, "Error while approving join-space request").also {
sendToast(e.msg())
}
}
)
}
}
)
}
}
fun onJoinAsReaderClicked() {
Timber.d("onJoinAsReaderClicked, state: ${state.value}")
private fun proceedGettingNewMember() {
viewModelScope.launch {
when(val curr = state.value) {
is State.Error -> {
// TODO send toast
val filters = ObjectSearchConstants.filterNewMember(vmParams.member)
searchObjects(
SearchObjects.Params(
filters = filters,
keys = ObjectSearchConstants.spaceMemberKeys,
limit = 1
)
).process(
failure = {
Timber.e(it, "Error while fetching new member")
_viewState.value = ViewState.Error.NewMemberError(it.msg())
},
success = { result ->
val memberMap = result.firstOrNull()?.map
if (memberMap.isNullOrEmpty()) {
_viewState.value = ViewState.Error.NewMemberError("New member not found")
} else {
_newMember.value =
NewMemberState.Success(ObjectWrapper.SpaceMember(memberMap))
}
}
is State.Init -> {
// Do nothing
}
is State.Success -> {
approveJoinSpaceRequest.async(
ApproveJoinSpaceRequest.Params(
space = params.space,
identity = curr.member.identity,
permissions = SpaceMemberPermissions.READER
)
).fold(
onSuccess = {
analytics.sendEvent(
eventName = EventsDictionary.approveInviteRequest,
props = Props(
mapOf(
EventsPropertiesKey.type to "Read"
)
)
)
isDismissed.value = true
},
onFailure = { e ->
Timber.e(e, "Error while approving join-space request").also {
sendToast(e.msg())
}
}
)
}
}
)
}
}
fun onJoinAsEditorClicked() {
Timber.d("onJoinAsEditorClicked, state: ${state.value}")
fun onRejectRequestClicked(newMember: Id) {
Timber.d("onRejectRequestClicked, newMember: $newMember")
viewModelScope.launch {
when(val curr = state.value) {
is State.Error -> {
// TODO send toast
declineSpaceJoinRequest.async(
DeclineSpaceJoinRequest.Params(
space = vmParams.space,
identity = newMember
)
).fold(
onSuccess = {
analytics.sendEvent(eventName = rejectInviteRequest)
isDismissed.value = true
},
onFailure = { e ->
Timber.e(e, "Error while rejecting join-space request").also {
sendToast(e.msg())
}
}
is State.Init -> {
// Do nothing
}
is State.Success -> {
approveJoinSpaceRequest.async(
ApproveJoinSpaceRequest.Params(
space = params.space,
identity = curr.member.identity,
permissions = SpaceMemberPermissions.WRITER
)
).fold(
onSuccess = {
analytics.sendEvent(
eventName = EventsDictionary.approveInviteRequest,
props = Props(
mapOf(
EventsPropertiesKey.type to "Write"
)
)
)
isDismissed.value = true
},
onFailure = { e ->
Timber.e(e, "Error while approving join-space request").also {
sendToast(e.msg())
}
}
)
)
}
}
private fun onJoinClicked(newMember: Id, permissions: SpaceMemberPermissions) {
Timber.d("onJoinClicked, newMember: $newMember, permissions: $permissions")
viewModelScope.launch {
approveJoinSpaceRequest.async(
ApproveJoinSpaceRequest.Params(
space = vmParams.space,
identity = newMember,
permissions = permissions
)
).fold(
onSuccess = {
analytics.sendAnalyticsApproveInvite(permissions)
isDismissed.value = true
},
onFailure = { e ->
Timber.e(e, "Error while approving join-space request").also {
sendToast(e.msg())
}
}
)
}
}
fun onJoinAsReaderClicked(newMember: Id) {
onJoinClicked(newMember, SpaceMemberPermissions.READER)
}
fun onJoinAsEditorClicked(newMember: Id) {
onJoinClicked(newMember, SpaceMemberPermissions.WRITER)
}
fun onUpgradeClicked() {
val activeTier = (_activeTier.value as? ActiveTierState.Success) ?: return
val isPossibleToUpgrade = activeTier.tierId.isPossibleToUpgradeNumberOfSpaceMembers()
viewModelScope.launch {
if (isPossibleToUpgrade) {
_commands.emit(Command.NavigateToMembership)
} else {
//todo navigate to membership email screen
}
}
}
class Factory @Inject constructor(
private val params: Params,
private val params: VmParams,
private val approveJoinSpaceRequest: ApproveJoinSpaceRequest,
private val declineSpaceJoinRequest: DeclineSpaceJoinRequest,
private val searchObjects: SearchObjects,
private val spaceManager: SpaceManager,
private val urlBuilder: UrlBuilder,
private val analytics: Analytics,
private val userPermissionProvider: UserPermissionProvider
private val userPermissionProvider: UserPermissionProvider,
private val spaceViewSubscriptionContainer: SpaceViewSubscriptionContainer,
private val membershipProvider: MembershipProvider
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T = SpaceJoinRequestViewModel(
params = params,
vmParams = params,
declineSpaceJoinRequest = declineSpaceJoinRequest,
approveJoinSpaceRequest = approveJoinSpaceRequest,
searchObjects = searchObjects,
spaceManager = spaceManager,
urlBuilder = urlBuilder,
analytics = analytics,
userPermissionProvider = userPermissionProvider
userPermissionProvider = userPermissionProvider,
spaceViewSubscriptionContainer = spaceViewSubscriptionContainer,
membershipProvider = membershipProvider
) as T
}
data class Params(val space: SpaceId, val member: Id, val route: String)
data class VmParams(val space: SpaceId, val member: Id, val route: String)
sealed class State {
object Init : State()
data class Success(
val member: ObjectWrapper.SpaceMember,
val spaceView: ObjectWrapper.SpaceView,
val participants: List<ObjectWrapper.SpaceMember>
) : State()
object Error : State()
data class Result(
val isCurrentUserOwner: Boolean,
val spaceView: ObjectWrapper.SpaceView,
val tierId: TierId,
val spaceMembers: List<ObjectWrapper.SpaceMember>,
val newMember: ObjectWrapper.SpaceMember
)
//We're trying to add or remove new member to this space
sealed class SpaceViewState {
data object Init : SpaceViewState()
data class Success(val spaceView: ObjectWrapper.SpaceView) : SpaceViewState()
}
//All participants of this space
sealed class SpaceMembersState {
data object Init : SpaceMembersState()
data class Success(val spaceMembers: List<ObjectWrapper.SpaceMember>) : SpaceMembersState()
}
//New member that we're trying to add or remove
sealed class NewMemberState {
data object Init : NewMemberState()
data class Success(val newMember: ObjectWrapper.SpaceMember) : NewMemberState()
}
//Active membership status of the current user
sealed class ActiveTierState {
data object Init : ActiveTierState()
data class Success(val tierId: TierId) : ActiveTierState()
}
sealed class ViewState {
object Init: ViewState()
data object Init : ViewState()
data class Success(
val memberName: String,
val newMember: Id,
val newMemberName: String,
val spaceName: String,
val icon: SpaceMemberIconView,
val canAddAsReader: Boolean,
val canAddAsEditor: Boolean
): ViewState()
object Error: ViewState()
val buttons: List<InviteButton>
) : ViewState()
sealed class Error : ViewState() {
data class SpaceParticipantsError(val message: String) : Error()
data class ActiveTierError(val message: String) : Error()
data class CurrentUserStatusError(val message: String) : Error()
data class NewMemberError(val message: String) : Error()
}
}
sealed class Command {
data object NavigateToMembership : Command()
}
}
enum class InviteButton {
REJECT,
JOIN_AS_VIEWER,
JOIN_AS_VIEWER_DISABLED,
ADD_MORE_VIEWERS,
JOIN_AS_EDITOR,
JOIN_AS_EDITOR_DISABLED,
ADD_MORE_EDITORS,
UPGRADE,
}

View file

@ -807,6 +807,30 @@ object ObjectSearchConstants {
)
}
fun filterNewMember(member: Id) : List<DVFilter> = buildList {
add(
DVFilter(
relation = Relations.IS_ARCHIVED,
condition = DVFilterCondition.NOT_EQUAL,
value = true
)
)
add(
DVFilter(
relation = Relations.IS_DELETED,
condition = DVFilterCondition.NOT_EQUAL,
value = true
)
)
add(
DVFilter(
relation = Relations.ID,
condition = DVFilterCondition.IN,
value = listOf(member)
)
)
}
fun defaultDataViewFilters(spaces: List<Id>) = buildList {
add(
DVFilter(
@ -1132,6 +1156,7 @@ object ObjectSearchConstants {
val spaceMemberKeys = listOf(
Relations.ID,
Relations.SPACE_ID,
Relations.TARGET_SPACE_ID,
Relations.IDENTITY,
Relations.NAME,
Relations.ICON_IMAGE,

View file

@ -49,7 +49,6 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.rememberAsyncImagePainter
import com.anytypeio.anytype.core_models.NetworkMode
import com.anytypeio.anytype.core_ui.foundation.Arrow
import com.anytypeio.anytype.core_ui.foundation.Divider
import com.anytypeio.anytype.core_ui.foundation.Dragger
@ -59,7 +58,7 @@ import com.anytypeio.anytype.core_ui.foundation.noRippleClickable
import com.anytypeio.anytype.core_ui.views.BodyRegular
import com.anytypeio.anytype.core_ui.views.Caption1Regular
import com.anytypeio.anytype.core_ui.views.Title1
import com.anytypeio.anytype.presentation.membership.models.MembershipStatus
import com.anytypeio.anytype.core_models.membership.MembershipStatus
import com.anytypeio.anytype.presentation.profile.ProfileIconView
import com.anytypeio.anytype.ui_settings.R
import kotlinx.coroutines.FlowPreview

View file

@ -25,7 +25,7 @@ import com.anytypeio.anytype.domain.`object`.SetObjectDetails
import com.anytypeio.anytype.domain.search.PROFILE_SUBSCRIPTION_ID
import com.anytypeio.anytype.presentation.common.BaseViewModel
import com.anytypeio.anytype.presentation.extension.sendScreenSettingsDeleteEvent
import com.anytypeio.anytype.presentation.membership.models.MembershipStatus
import com.anytypeio.anytype.core_models.membership.MembershipStatus
import com.anytypeio.anytype.presentation.membership.provider.MembershipProvider
import com.anytypeio.anytype.presentation.profile.ProfileIconView
import com.anytypeio.anytype.presentation.profile.profileIcon