diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/MainEntryDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/MainEntryDI.kt index 8a09770739..45187282f7 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/MainEntryDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/MainEntryDI.kt @@ -25,6 +25,7 @@ import com.anytypeio.anytype.domain.wallpaper.RestoreWallpaper import com.anytypeio.anytype.domain.wallpaper.WallpaperStore import com.anytypeio.anytype.domain.workspace.SpaceManager import com.anytypeio.anytype.presentation.main.MainViewModelFactory +import com.anytypeio.anytype.presentation.membership.provider.MembershipProvider import com.anytypeio.anytype.presentation.notifications.NotificationsProvider import com.anytypeio.anytype.ui.main.MainActivity import com.anytypeio.anytype.ui_settings.appearance.ThemeApplicator @@ -67,7 +68,8 @@ object MainEntryModule { spaceDeletedStatusWatcher: SpaceDeletedStatusWatcher, localeProvider: LocaleProvider, userPermissionProvider: UserPermissionProvider, - notificationsProvider: NotificationsProvider + notificationsProvider: NotificationsProvider, + membershipProvider: MembershipProvider ): MainViewModelFactory = MainViewModelFactory( resumeAccount = resumeAccount, analytics = analytics, @@ -82,7 +84,8 @@ object MainEntryModule { spaceDeletedStatusWatcher = spaceDeletedStatusWatcher, localeProvider = localeProvider, userPermissionProvider = userPermissionProvider, - notificationsProvider = notificationsProvider + notificationsProvider = notificationsProvider, + membershipProvider = membershipProvider ) @JvmStatic diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/payments/PaymentsDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/payments/PaymentsDI.kt index d1d8ba626f..59ff1ea34d 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/payments/PaymentsDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/payments/PaymentsDI.kt @@ -8,6 +8,8 @@ import com.anytypeio.anytype.di.common.ComponentDependencies import com.anytypeio.anytype.domain.auth.interactor.GetAccount 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.payments.GetMembershipTiers import com.anytypeio.anytype.payments.playbilling.BillingClientLifecycle import com.anytypeio.anytype.ui.payments.PaymentsFragment import com.anytypeio.anytype.payments.viewmodel.PaymentsViewModelFactory @@ -45,6 +47,14 @@ object PaymentsModule { dispatchers: AppCoroutineDispatchers ): GetAccount = GetAccount(repo = repo, dispatcher = dispatchers) + @JvmStatic + @Provides + @PerScreen + fun provideGetTiersUseCase( + repo: BlockRepository, + dispatchers: AppCoroutineDispatchers + ): GetMembershipTiers = GetMembershipTiers(repo = repo, dispatchers = dispatchers) + @Module interface Declarations { @@ -62,5 +72,6 @@ interface PaymentsComponentDependencies : ComponentDependencies { fun context(): Context fun billingListener(): BillingClientLifecycle fun authRepository(): AuthRepository + fun blockRepository(): BlockRepository fun appCoroutineDispatchers(): AppCoroutineDispatchers } \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/di/main/BillingModule.kt b/app/src/main/java/com/anytypeio/anytype/di/main/BillingModule.kt deleted file mode 100644 index 7520cf567b..0000000000 --- a/app/src/main/java/com/anytypeio/anytype/di/main/BillingModule.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.anytypeio.anytype.di.main - -import android.content.Context -import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers -import com.anytypeio.anytype.payments.playbilling.BillingClientLifecycle -import dagger.Module -import dagger.Provides -import javax.inject.Named -import javax.inject.Singleton -import kotlinx.coroutines.CoroutineScope - -@Module -object BillingModule { - - @Singleton - @Provides - fun provideBillingLifecycle( - context: Context, - dispatchers: AppCoroutineDispatchers, - @Named(ConfigModule.DEFAULT_APP_COROUTINE_SCOPE) scope: CoroutineScope - ): BillingClientLifecycle { - return BillingClientLifecycle( - dispatchers = dispatchers, - applicationContext = context, - scope = scope - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/di/main/DeviceModule.kt b/app/src/main/java/com/anytypeio/anytype/di/main/DeviceModule.kt index f861b84051..a2c413ce01 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/main/DeviceModule.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/main/DeviceModule.kt @@ -11,7 +11,6 @@ import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers import com.anytypeio.anytype.domain.device.FileSharer import com.anytypeio.anytype.domain.download.Downloader import com.anytypeio.anytype.domain.misc.LocaleProvider -import com.anytypeio.anytype.providers.DefaultUriFileProvider import dagger.Module import dagger.Provides import javax.inject.Singleton diff --git a/app/src/main/java/com/anytypeio/anytype/di/main/MainComponent.kt b/app/src/main/java/com/anytypeio/anytype/di/main/MainComponent.kt index ff59906ecb..ea702f8c18 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/main/MainComponent.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/main/MainComponent.kt @@ -80,7 +80,7 @@ import javax.inject.Singleton TemplatesModule::class, NetworkModeModule::class, NotificationsModule::class, - BillingModule::class + MembershipModule::class ] ) interface MainComponent : diff --git a/app/src/main/java/com/anytypeio/anytype/di/main/MembershipModule.kt b/app/src/main/java/com/anytypeio/anytype/di/main/MembershipModule.kt new file mode 100644 index 0000000000..3806d0b1e7 --- /dev/null +++ b/app/src/main/java/com/anytypeio/anytype/di/main/MembershipModule.kt @@ -0,0 +1,72 @@ +package com.anytypeio.anytype.di.main + +import android.content.Context +import com.anytypeio.anytype.data.auth.event.MembershipDateChannel +import com.anytypeio.anytype.data.auth.event.MembershipRemoteChannel +import com.anytypeio.anytype.domain.account.AwaitAccountStartManager +import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers +import com.anytypeio.anytype.domain.block.repo.BlockRepository +import com.anytypeio.anytype.domain.misc.LocaleProvider +import com.anytypeio.anytype.domain.workspace.MembershipChannel +import com.anytypeio.anytype.middleware.EventProxy +import com.anytypeio.anytype.middleware.interactor.MembershipMiddlewareChannel +import com.anytypeio.anytype.payments.playbilling.BillingClientLifecycle +import com.anytypeio.anytype.presentation.membership.provider.MembershipProvider +import dagger.Module +import dagger.Provides +import javax.inject.Named +import javax.inject.Singleton +import kotlinx.coroutines.CoroutineScope + +@Module +object MembershipModule { + + @Singleton + @Provides + fun provideBillingLifecycle( + context: Context, + dispatchers: AppCoroutineDispatchers, + @Named(ConfigModule.DEFAULT_APP_COROUTINE_SCOPE) scope: CoroutineScope + ): BillingClientLifecycle { + return BillingClientLifecycle( + dispatchers = dispatchers, + applicationContext = context, + scope = scope + ) + } + + @JvmStatic + @Provides + @Singleton + fun provideMembershipRemoteChannel( + proxy: EventProxy + ): MembershipRemoteChannel = MembershipMiddlewareChannel( + eventsProxy = proxy + ) + + @JvmStatic + @Provides + @Singleton + fun provideMembershipChannel( + channel: MembershipRemoteChannel + ): MembershipChannel = MembershipDateChannel( + channel = channel + ) + + @JvmStatic + @Singleton + @Provides + fun provideMembershipProvider( + dispatchers: AppCoroutineDispatchers, + awaitAccountStartManager: AwaitAccountStartManager, + membershipChannel: MembershipChannel, + localeProvider: LocaleProvider, + repo: BlockRepository + ): MembershipProvider = MembershipProvider.Default( + dispatchers = dispatchers, + membershipChannel = membershipChannel, + awaitAccountStartManager = awaitAccountStartManager, + localeProvider = localeProvider, + repo = repo + ) +} \ No newline at end of file diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/membership/MembershipModels.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/membership/MembershipModels.kt index b5e23123a7..7c920c6629 100644 --- a/core-models/src/main/java/com/anytypeio/anytype/core_models/membership/MembershipModels.kt +++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/membership/MembershipModels.kt @@ -2,7 +2,7 @@ package com.anytypeio.anytype.core_models.membership data class Membership( val tier: Int, - val membershipStatus: MembershipStatus, + val membershipStatusModel: MembershipStatusModel, val dateStarted: Long, val dateEnds: Long, val isAutoRenew: Boolean, @@ -10,9 +10,12 @@ data class Membership( val requestedAnyName: String, val userEmail: String, val subscribeToNewsletter: Boolean -) +) { -enum class MembershipStatus { + data class Event(val membership: Membership) +} + +enum class MembershipStatusModel { STATUS_UNKNOWN, STATUS_PENDING, STATUS_ACTIVE, diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/event/MembershipRemoteChannel.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/event/MembershipRemoteChannel.kt new file mode 100644 index 0000000000..bbc504e024 --- /dev/null +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/event/MembershipRemoteChannel.kt @@ -0,0 +1,18 @@ +package com.anytypeio.anytype.data.auth.event + +import com.anytypeio.anytype.core_models.membership.Membership +import com.anytypeio.anytype.domain.workspace.MembershipChannel +import kotlinx.coroutines.flow.Flow + +interface MembershipRemoteChannel { + fun observe(): Flow> +} + +class MembershipDateChannel( + private val channel: MembershipRemoteChannel +) : MembershipChannel { + + override fun observe(): Flow> { + return channel.observe() + } +} \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/payments/GetMembershipStatus.kt b/domain/src/main/java/com/anytypeio/anytype/domain/payments/GetMembershipStatus.kt new file mode 100644 index 0000000000..b8206a8a84 --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/payments/GetMembershipStatus.kt @@ -0,0 +1,25 @@ +package com.anytypeio.anytype.domain.payments + +import com.anytypeio.anytype.core_models.Command +import com.anytypeio.anytype.core_models.membership.Membership +import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers +import com.anytypeio.anytype.domain.base.ResultInteractor +import com.anytypeio.anytype.domain.block.repo.BlockRepository +import javax.inject.Inject + +class GetMembershipStatus @Inject constructor( + dispatchers: AppCoroutineDispatchers, + private val repo: BlockRepository +) : ResultInteractor(dispatchers.io) { + + override suspend fun doWork(params: Params): Membership? { + val command = Command.Membership.GetStatus( + noCache = params.noCache + ) + return repo.membershipStatus(command) + } + + data class Params( + val noCache: Boolean + ) +} \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/payments/GetTiers.kt b/domain/src/main/java/com/anytypeio/anytype/domain/payments/GetMembershipTiers.kt similarity index 85% rename from domain/src/main/java/com/anytypeio/anytype/domain/payments/GetTiers.kt rename to domain/src/main/java/com/anytypeio/anytype/domain/payments/GetMembershipTiers.kt index 61b4cd38e9..b9eaa66698 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/payments/GetTiers.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/payments/GetMembershipTiers.kt @@ -7,10 +7,10 @@ import com.anytypeio.anytype.domain.base.ResultInteractor import com.anytypeio.anytype.domain.block.repo.BlockRepository import javax.inject.Inject -class GetTiers @Inject constructor( +class GetMembershipTiers @Inject constructor( dispatchers: AppCoroutineDispatchers, private val repo: BlockRepository -) : ResultInteractor>(dispatchers.io) { +) : ResultInteractor>(dispatchers.io) { override suspend fun doWork(params: Params): List { val command = Command.Membership.GetTiers( diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/workspace/MembershipChannel.kt b/domain/src/main/java/com/anytypeio/anytype/domain/workspace/MembershipChannel.kt new file mode 100644 index 0000000000..675ea5e8d1 --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/workspace/MembershipChannel.kt @@ -0,0 +1,8 @@ +package com.anytypeio.anytype.domain.workspace + +import com.anytypeio.anytype.core_models.membership.Membership +import kotlinx.coroutines.flow.Flow + +interface MembershipChannel { + fun observe(): Flow> +} \ No newline at end of file diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/MembershipMiddlewareChannel.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/MembershipMiddlewareChannel.kt new file mode 100644 index 0000000000..b6af4c4f1b --- /dev/null +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/MembershipMiddlewareChannel.kt @@ -0,0 +1,37 @@ +package com.anytypeio.anytype.middleware.interactor + +import com.anytypeio.anytype.core_models.membership.Membership +import com.anytypeio.anytype.data.auth.event.MembershipRemoteChannel +import com.anytypeio.anytype.middleware.EventProxy +import com.anytypeio.anytype.middleware.mappers.toCoreModel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.mapNotNull + +class MembershipMiddlewareChannel( + private val eventsProxy: EventProxy +): MembershipRemoteChannel { + + override fun observe(): Flow> { + return eventsProxy.flow() + .mapNotNull { emission -> + emission.messages.mapNotNull { message -> + when { + message.membershipUpdate != null -> { + val event = message.membershipUpdate + checkNotNull(event) + val membership = event.data_ + if (membership != null) { + Membership.Event( + membership = membership.toCoreModel() + ) + } else { + null + } + } + else -> null + } + } + }.filter { events -> events.isNotEmpty() } + } +} \ No newline at end of file diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToCoreModelMappers.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToCoreModelMappers.kt index fa5d34c8df..8e0a8db553 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToCoreModelMappers.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToCoreModelMappers.kt @@ -46,7 +46,7 @@ import com.anytypeio.anytype.core_models.membership.EmailVerificationStatus 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.MembershipStatus +import com.anytypeio.anytype.core_models.membership.MembershipStatusModel import com.anytypeio.anytype.core_models.membership.MembershipTierData import com.anytypeio.anytype.core_models.multiplayer.SpaceMemberPermissions import com.anytypeio.anytype.core_models.primitives.SpaceId @@ -975,12 +975,12 @@ fun MNotification.toCoreModel(): Notification { //endregion //region MEMBERSHIP -fun MMembershipStatus.toCoreModel(): MembershipStatus { +fun MMembershipStatus.toCoreModel(): MembershipStatusModel { return when (this) { - MMembershipStatus.StatusUnknown -> MembershipStatus.STATUS_UNKNOWN - MMembershipStatus.StatusPending -> MembershipStatus.STATUS_PENDING - MMembershipStatus.StatusActive -> MembershipStatus.STATUS_ACTIVE - MMembershipStatus.StatusPendingRequiresFinalization -> MembershipStatus.STATUS_PENDING_FINALIZATION + MMembershipStatus.StatusUnknown -> MembershipStatusModel.STATUS_UNKNOWN + MMembershipStatus.StatusPending -> MembershipStatusModel.STATUS_PENDING + MMembershipStatus.StatusActive -> MembershipStatusModel.STATUS_ACTIVE + MMembershipStatus.StatusPendingRequiresFinalization -> MembershipStatusModel.STATUS_PENDING_FINALIZATION } } @@ -997,7 +997,7 @@ fun MMembershipPaymentMethod.toCoreModel(): MembershipPaymentMethod { fun MMembership.toCoreModel(): Membership { return Membership( tier = tier, - membershipStatus = status.toCoreModel(), + membershipStatusModel = status.toCoreModel(), dateStarted = dateStarted, dateEnds = dateEnds, isAutoRenew = isAutoRenew, diff --git a/payments/src/main/java/com/anytypeio/anytype/payments/screens/EnterCode.kt b/payments/src/main/java/com/anytypeio/anytype/payments/screens/EnterCode.kt index 34fb5d793a..fd3b89db72 100644 --- a/payments/src/main/java/com/anytypeio/anytype/payments/screens/EnterCode.kt +++ b/payments/src/main/java/com/anytypeio/anytype/payments/screens/EnterCode.kt @@ -52,7 +52,7 @@ import com.anytypeio.anytype.core_ui.views.HeadlineTitle import com.anytypeio.anytype.core_ui.views.PreviewTitle1Regular import com.anytypeio.anytype.payments.R import com.anytypeio.anytype.payments.viewmodel.PaymentsCodeState -import com.anytypeio.anytype.payments.viewmodel.TierId +import com.anytypeio.anytype.presentation.membership.models.TierId @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/payments/src/main/java/com/anytypeio/anytype/payments/screens/MainPaymensScreen.kt b/payments/src/main/java/com/anytypeio/anytype/payments/screens/MainPaymensScreen.kt index f770c48bde..8d2c763bf1 100644 --- a/payments/src/main/java/com/anytypeio/anytype/payments/screens/MainPaymensScreen.kt +++ b/payments/src/main/java/com/anytypeio/anytype/payments/screens/MainPaymensScreen.kt @@ -53,9 +53,9 @@ import com.anytypeio.anytype.core_ui.views.BodyRegular import com.anytypeio.anytype.core_ui.views.Caption1Regular import com.anytypeio.anytype.core_ui.views.Relations2 import com.anytypeio.anytype.core_ui.views.fontRiccioneRegular -import com.anytypeio.anytype.payments.models.Tier +import com.anytypeio.anytype.presentation.membership.models.Tier import com.anytypeio.anytype.payments.viewmodel.PaymentsMainState -import com.anytypeio.anytype.payments.viewmodel.TierId +import com.anytypeio.anytype.presentation.membership.models.TierId @Composable fun MainPaymentsScreen(state: PaymentsMainState, tierClicked: (TierId) -> Unit) { diff --git a/payments/src/main/java/com/anytypeio/anytype/payments/screens/MembershipLevel.kt b/payments/src/main/java/com/anytypeio/anytype/payments/screens/MembershipLevel.kt index ab31fadfa0..3dd559cb25 100644 --- a/payments/src/main/java/com/anytypeio/anytype/payments/screens/MembershipLevel.kt +++ b/payments/src/main/java/com/anytypeio/anytype/payments/screens/MembershipLevel.kt @@ -58,9 +58,9 @@ import com.anytypeio.anytype.core_ui.views.HeadlineTitle import com.anytypeio.anytype.core_ui.views.Relations1 import com.anytypeio.anytype.core_ui.views.Relations2 import com.anytypeio.anytype.payments.R -import com.anytypeio.anytype.payments.models.Tier +import com.anytypeio.anytype.presentation.membership.models.Tier import com.anytypeio.anytype.payments.viewmodel.PaymentsTierState -import com.anytypeio.anytype.payments.viewmodel.TierId +import com.anytypeio.anytype.presentation.membership.models.TierId @OptIn(ExperimentalMaterial3Api::class) diff --git a/payments/src/main/java/com/anytypeio/anytype/payments/screens/PaymentWelcomeScreen.kt b/payments/src/main/java/com/anytypeio/anytype/payments/screens/PaymentWelcomeScreen.kt index 54676eb9eb..bc832f529f 100644 --- a/payments/src/main/java/com/anytypeio/anytype/payments/screens/PaymentWelcomeScreen.kt +++ b/payments/src/main/java/com/anytypeio/anytype/payments/screens/PaymentWelcomeScreen.kt @@ -27,9 +27,9 @@ import com.anytypeio.anytype.core_ui.views.ButtonSecondary import com.anytypeio.anytype.core_ui.views.ButtonSize import com.anytypeio.anytype.core_ui.views.HeadlineHeading import com.anytypeio.anytype.payments.R -import com.anytypeio.anytype.payments.models.Tier +import com.anytypeio.anytype.presentation.membership.models.Tier import com.anytypeio.anytype.payments.viewmodel.PaymentsWelcomeState -import com.anytypeio.anytype.payments.viewmodel.TierId +import com.anytypeio.anytype.presentation.membership.models.TierId @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/payments/src/main/java/com/anytypeio/anytype/payments/screens/TierView.kt b/payments/src/main/java/com/anytypeio/anytype/payments/screens/TierView.kt index 55258caf05..d132c1fdc0 100644 --- a/payments/src/main/java/com/anytypeio/anytype/payments/screens/TierView.kt +++ b/payments/src/main/java/com/anytypeio/anytype/payments/screens/TierView.kt @@ -37,7 +37,7 @@ 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.models.Tier +import com.anytypeio.anytype.presentation.membership.models.Tier @Composable fun TierView( diff --git a/payments/src/main/java/com/anytypeio/anytype/payments/viewmodel/PaymentsMainState.kt b/payments/src/main/java/com/anytypeio/anytype/payments/viewmodel/PaymentsMainState.kt index 5fe37cd2c3..07c0afec62 100644 --- a/payments/src/main/java/com/anytypeio/anytype/payments/viewmodel/PaymentsMainState.kt +++ b/payments/src/main/java/com/anytypeio/anytype/payments/viewmodel/PaymentsMainState.kt @@ -1,6 +1,7 @@ package com.anytypeio.anytype.payments.viewmodel -import com.anytypeio.anytype.payments.models.Tier +import com.anytypeio.anytype.presentation.membership.models.Tier +import com.anytypeio.anytype.presentation.membership.models.TierId sealed class PaymentsMainState { @@ -44,7 +45,4 @@ sealed class PaymentsNavigation(val route: String) { object Code : PaymentsNavigation("code") object Welcome : PaymentsNavigation("welcome") object Dismiss : PaymentsNavigation("") -} - -@JvmInline -value class TierId(val value: String) \ No newline at end of file +} \ No newline at end of file diff --git a/payments/src/main/java/com/anytypeio/anytype/payments/viewmodel/PaymentsViewModel.kt b/payments/src/main/java/com/anytypeio/anytype/payments/viewmodel/PaymentsViewModel.kt index 18e51b5156..63a807e582 100644 --- a/payments/src/main/java/com/anytypeio/anytype/payments/viewmodel/PaymentsViewModel.kt +++ b/payments/src/main/java/com/anytypeio/anytype/payments/viewmodel/PaymentsViewModel.kt @@ -7,9 +7,12 @@ import com.android.billingclient.api.ProductDetails import com.android.billingclient.api.Purchase import com.anytypeio.anytype.analytics.base.Analytics import com.anytypeio.anytype.domain.auth.interactor.GetAccount +import com.anytypeio.anytype.domain.base.fold +import com.anytypeio.anytype.domain.payments.GetMembershipTiers import com.anytypeio.anytype.payments.constants.BillingConstants -import com.anytypeio.anytype.payments.models.Tier +import com.anytypeio.anytype.presentation.membership.models.Tier import com.anytypeio.anytype.payments.playbilling.BillingClientLifecycle +import com.anytypeio.anytype.presentation.membership.models.TierId import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow @@ -19,7 +22,8 @@ import timber.log.Timber class PaymentsViewModel( private val analytics: Analytics, private val billingClientLifecycle: BillingClientLifecycle, - private val getAccount: GetAccount + private val getAccount: GetAccount, + private val getMembershipTiers: GetMembershipTiers ) : ViewModel() { val viewState = MutableStateFlow(PaymentsMainState.Loading) @@ -50,9 +54,19 @@ class PaymentsViewModel( init { Timber.d("PaymentsViewModel init") - _tiers.addAll(gertTiers()) + proceedWithGetTiers() setupActiveTierName() - viewState.value = PaymentsMainState.Default(_tiers) + } + + private fun proceedWithGetTiers() { + viewModelScope.launch { + getMembershipTiers.async(GetMembershipTiers.Params("en", false)).fold( + onSuccess = { result -> + ///todo handle the result + }, + onFailure = Timber::e + ) + } } fun onTierClicked(tierId: TierId) { diff --git a/payments/src/main/java/com/anytypeio/anytype/payments/viewmodel/PaymentsViewModelFactory.kt b/payments/src/main/java/com/anytypeio/anytype/payments/viewmodel/PaymentsViewModelFactory.kt index 6e09b2c6e0..a206d00456 100644 --- a/payments/src/main/java/com/anytypeio/anytype/payments/viewmodel/PaymentsViewModelFactory.kt +++ b/payments/src/main/java/com/anytypeio/anytype/payments/viewmodel/PaymentsViewModelFactory.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import com.anytypeio.anytype.analytics.base.Analytics import com.anytypeio.anytype.domain.auth.interactor.GetAccount +import com.anytypeio.anytype.domain.payments.GetMembershipTiers import com.anytypeio.anytype.payments.playbilling.BillingClientLifecycle import javax.inject.Inject @@ -11,13 +12,15 @@ class PaymentsViewModelFactory @Inject constructor( private val analytics: Analytics, private val billingClientLifecycle: BillingClientLifecycle, private val getAccount: GetAccount, + private val getMembershipTiers: GetMembershipTiers ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { return PaymentsViewModel( analytics = analytics, billingClientLifecycle = billingClientLifecycle, - getAccount = getAccount + getAccount = getAccount, + getMembershipTiers = getMembershipTiers ) as T } } \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/main/MainViewModelFactory.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/main/MainViewModelFactory.kt index bd837e5a86..620c9c1530 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/main/MainViewModelFactory.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/main/MainViewModelFactory.kt @@ -15,6 +15,7 @@ import com.anytypeio.anytype.domain.search.RelationsSubscriptionManager import com.anytypeio.anytype.domain.spaces.SpaceDeletedStatusWatcher import com.anytypeio.anytype.domain.wallpaper.ObserveWallpaper import com.anytypeio.anytype.domain.wallpaper.RestoreWallpaper +import com.anytypeio.anytype.presentation.membership.provider.MembershipProvider import com.anytypeio.anytype.presentation.notifications.NotificationsProvider import javax.inject.Inject @@ -32,7 +33,8 @@ class MainViewModelFactory @Inject constructor( private val spaceDeletedStatusWatcher: SpaceDeletedStatusWatcher, private val localeProvider: LocaleProvider, private val userPermissionProvider: UserPermissionProvider, - private val notificationsProvider: NotificationsProvider + private val notificationsProvider: NotificationsProvider, + private val membershipProvider: MembershipProvider ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create( @@ -51,6 +53,7 @@ class MainViewModelFactory @Inject constructor( spaceDeletedStatusWatcher = spaceDeletedStatusWatcher, localeProvider = localeProvider, userPermissionProvider = userPermissionProvider, - notificationsProvider = notificationsProvider + notificationsProvider = notificationsProvider, + membershipProvider = membershipProvider ) as T } \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/membership/models/MembershipStatus.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/membership/models/MembershipStatus.kt new file mode 100644 index 0000000000..02b2ed05ec --- /dev/null +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/membership/models/MembershipStatus.kt @@ -0,0 +1,21 @@ +package com.anytypeio.anytype.presentation.membership.models + +import com.anytypeio.anytype.core_models.membership.MembershipPaymentMethod +import com.anytypeio.anytype.core_models.membership.MembershipStatusModel +import com.anytypeio.anytype.core_models.membership.MembershipTierData + +sealed class MembershipStatus { + object Unknown : MembershipStatus() + object Pending : MembershipStatus() + object Finalization : MembershipStatus() + data class Active( + val tier: MembershipTierData?, + val status: MembershipStatusModel, + val dateEnds: Long, + val paymentMethod: MembershipPaymentMethod, + val anyName: String + ) : MembershipStatus() +} + +@JvmInline +value class TierId(val value: String) diff --git a/payments/src/main/java/com/anytypeio/anytype/payments/models/Tier.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/membership/models/Tier.kt similarity index 93% rename from payments/src/main/java/com/anytypeio/anytype/payments/models/Tier.kt rename to presentation/src/main/java/com/anytypeio/anytype/presentation/membership/models/Tier.kt index fb8489a7e4..b7463f7ab2 100644 --- a/payments/src/main/java/com/anytypeio/anytype/payments/models/Tier.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/membership/models/Tier.kt @@ -1,6 +1,5 @@ -package com.anytypeio.anytype.payments.models +package com.anytypeio.anytype.presentation.membership.models -import com.anytypeio.anytype.payments.viewmodel.TierId sealed class Tier { abstract val id: TierId diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/membership/provider/MembershipProvider.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/membership/provider/MembershipProvider.kt new file mode 100644 index 0000000000..277dfa48c9 --- /dev/null +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/membership/provider/MembershipProvider.kt @@ -0,0 +1,120 @@ +package com.anytypeio.anytype.presentation.membership.provider + +import com.anytypeio.anytype.core_models.Command +import com.anytypeio.anytype.core_models.membership.Membership +import com.anytypeio.anytype.core_models.membership.MembershipStatusModel.STATUS_ACTIVE +import com.anytypeio.anytype.core_models.membership.MembershipStatusModel.STATUS_PENDING +import com.anytypeio.anytype.core_models.membership.MembershipStatusModel.STATUS_PENDING_FINALIZATION +import com.anytypeio.anytype.core_models.membership.MembershipStatusModel.STATUS_UNKNOWN +import com.anytypeio.anytype.core_models.membership.MembershipTierData +import com.anytypeio.anytype.domain.account.AwaitAccountStartManager +import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers +import com.anytypeio.anytype.domain.block.repo.BlockRepository +import com.anytypeio.anytype.domain.misc.LocaleProvider +import com.anytypeio.anytype.domain.workspace.MembershipChannel +import com.anytypeio.anytype.presentation.membership.models.MembershipStatus +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.scan +import timber.log.Timber + +interface MembershipProvider { + + fun status(): Flow + + class Default( + private val dispatchers: AppCoroutineDispatchers, + private val membershipChannel: MembershipChannel, + private val awaitAccountStartManager: AwaitAccountStartManager, + private val localeProvider: LocaleProvider, + private val repo: BlockRepository + ) : MembershipProvider { + + @OptIn(ExperimentalCoroutinesApi::class) + override fun status(): Flow { + return awaitAccountStartManager.isStarted().flatMapLatest { isStarted -> + if (isStarted) { + buildStatusFlow( + initial = proceedWithGettingMembership() + ) + } else { + emptyFlow() + } + }.catch { e -> Timber.e(e) } + .flowOn(dispatchers.io) + } + + private fun buildStatusFlow( + initial: Membership? + ): Flow { + return membershipChannel + .observe() + .scan(initial) { _, events -> + events.lastOrNull()?.membership + }.mapNotNull { status -> + val tiers = proceedWithGettingTiers() + toMembershipStatus(status, tiers) + } + } + + private suspend fun proceedWithGettingMembership(): Membership? { + val command = Command.Membership.GetStatus( + noCache = false + ) + return repo.membershipStatus(command) + } + + private suspend fun proceedWithGettingTiers(): List { + val tiersParams = Command.Membership.GetTiers( + noCache = false, + locale = localeProvider.language() ?: DEFAULT_LOCALE + ) + return repo.membershipGetTiers(tiersParams) + } + + private fun toMembershipStatus( + membership: Membership?, + tiers: List + ): MembershipStatus { + return when (membership?.membershipStatusModel) { + STATUS_PENDING -> MembershipStatus.Pending + STATUS_PENDING_FINALIZATION -> MembershipStatus.Finalization + STATUS_ACTIVE -> toActiveMembershipStatus(membership, tiers) + STATUS_UNKNOWN, null -> { + Timber.e("Invalid or unknown membership status") + MembershipStatus.Unknown + } + + else -> MembershipStatus.Unknown + } + } + + private fun toActiveMembershipStatus( + membership: Membership, + tiers: List + ): MembershipStatus { + val tier = tiers.firstOrNull { it.id == membership.tier } + return if (tier != null) { + MembershipStatus.Active( + tier = tier, + status = membership.membershipStatusModel, + dateEnds = membership.dateEnds, + paymentMethod = membership.paymentMethod, + anyName = membership.requestedAnyName + ) + } else { + Timber.e("Membership tier not found: ${membership.tier}") + MembershipStatus.Unknown + } + } + + companion object { + const val DEFAULT_LOCALE = "en" + } + } +} \ No newline at end of file