From e34bb538f4bac65e60e4cf0b80f1816b61649e99 Mon Sep 17 00:00:00 2001 From: Konstantin Ivanov <54908981+konstantiniiv@users.noreply.github.com> Date: Mon, 24 Jun 2024 22:22:51 +0200 Subject: [PATCH] DROID-2378 Membership | Enhancement | Membership upgrade screen (#1312) --- .../anytype/di/common/ComponentManager.kt | 5 + .../feature/membership/MembershipUpdateDi.kt | 60 ++++++++++ .../anytype/di/main/MainComponent.kt | 9 +- .../multiplayer/SpaceJoinRequestFragment.kt | 11 +- .../ui/payments/MembershipUpgradeFragment.kt | 78 +++++++++++++ app/src/main/res/navigation/graph.xml | 4 + localization/src/main/res/values/strings.xml | 8 +- .../screens/MembershipUpgradeScreen.kt | 108 ++++++++++++++++++ .../membership/MembershipUpgradeViewModel.kt | 40 +++++++ .../multiplayer/SpaceJoinRequestViewModel.kt | 3 +- .../editor/editor/EditorCreateBlockTest.kt | 2 +- 11 files changed, 319 insertions(+), 9 deletions(-) create mode 100644 app/src/main/java/com/anytypeio/anytype/di/feature/membership/MembershipUpdateDi.kt create mode 100644 app/src/main/java/com/anytypeio/anytype/ui/payments/MembershipUpgradeFragment.kt create mode 100644 payments/src/main/java/com/anytypeio/anytype/payments/screens/MembershipUpgradeScreen.kt create mode 100644 presentation/src/main/java/com/anytypeio/anytype/presentation/membership/MembershipUpgradeViewModel.kt diff --git a/app/src/main/java/com/anytypeio/anytype/di/common/ComponentManager.kt b/app/src/main/java/com/anytypeio/anytype/di/common/ComponentManager.kt index c214e1cf08..75616c4013 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/common/ComponentManager.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/common/ComponentManager.kt @@ -64,6 +64,7 @@ import com.anytypeio.anytype.di.feature.gallery.DaggerGalleryInstallationCompone import com.anytypeio.anytype.di.feature.home.DaggerHomeScreenComponent import com.anytypeio.anytype.di.feature.library.DaggerLibraryComponent import com.anytypeio.anytype.di.feature.membership.DaggerMembershipComponent +import com.anytypeio.anytype.di.feature.membership.DaggerMembershipUpdateComponent import com.anytypeio.anytype.di.feature.multiplayer.DaggerRequestJoinSpaceComponent import com.anytypeio.anytype.di.feature.multiplayer.DaggerShareSpaceComponent import com.anytypeio.anytype.di.feature.multiplayer.DaggerSpaceJoinRequestComponent @@ -1143,6 +1144,10 @@ class ComponentManager( DaggerMembershipComponent.factory().create(findComponentDependencies()) } + val membershipUpgradeComponent = Component { + DaggerMembershipUpdateComponent.factory().create(findComponentDependencies()) + } + val galleryInstallationsComponent = ComponentWithParams { params: GalleryInstallationViewModel.ViewModelParams -> DaggerGalleryInstallationComponent.builder() diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/membership/MembershipUpdateDi.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/membership/MembershipUpdateDi.kt new file mode 100644 index 0000000000..3eb183f8bb --- /dev/null +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/membership/MembershipUpdateDi.kt @@ -0,0 +1,60 @@ +package com.anytypeio.anytype.di.feature.membership + +import androidx.lifecycle.ViewModelProvider +import com.anytypeio.anytype.core_utils.di.scope.PerScreen +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.presentation.membership.MembershipUpgradeViewModel +import com.anytypeio.anytype.ui.payments.MembershipUpgradeFragment +import dagger.Binds +import dagger.Component +import dagger.Module +import dagger.Provides + +@Component( + dependencies = [MembershipUpdateComponentDependencies::class], + modules = [ + MembershipUpdateModule::class, + MembershipUpdateModule.Declarations::class + ] +) +@PerScreen +interface MembershipUpdateComponent { + + @Component.Factory + interface Factory { + fun create(dependencies: MembershipUpdateComponentDependencies): MembershipUpdateComponent + } + + fun inject(fragment: MembershipUpgradeFragment) +} + +@Module +object MembershipUpdateModule { + + @JvmStatic + @Provides + @PerScreen + fun provideGetAccountUseCase( + repo: AuthRepository, + dispatchers: AppCoroutineDispatchers + ): GetAccount = GetAccount(repo = repo, dispatcher = dispatchers) + + @Module + interface Declarations { + + @PerScreen + @Binds + fun bindViewModelFactory( + factory: MembershipUpgradeViewModel.Factory + ): ViewModelProvider.Factory + + } +} + +interface MembershipUpdateComponentDependencies : ComponentDependencies { + fun authRepository(): AuthRepository + fun appCoroutineDispatchers(): AppCoroutineDispatchers +} \ No newline at end of file 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 41fc33a281..e0c0cee94c 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 @@ -33,6 +33,7 @@ import com.anytypeio.anytype.di.feature.onboarding.login.OnboardingMnemonicLogin import com.anytypeio.anytype.di.feature.onboarding.signup.OnboardingMnemonicDependencies import com.anytypeio.anytype.di.feature.onboarding.signup.OnboardingSoulCreationDependencies import com.anytypeio.anytype.di.feature.membership.MembershipComponentDependencies +import com.anytypeio.anytype.di.feature.membership.MembershipUpdateComponentDependencies import com.anytypeio.anytype.di.feature.relations.RelationCreateFromLibraryDependencies import com.anytypeio.anytype.di.feature.relations.RelationEditDependencies import com.anytypeio.anytype.di.feature.search.GlobalSearchDependencies @@ -122,7 +123,8 @@ interface MainComponent : MembershipComponentDependencies, GalleryInstallationComponentDependencies, NotificationDependencies, - GlobalSearchDependencies + GlobalSearchDependencies, + MembershipUpdateComponentDependencies { fun inject(app: AndroidApplication) @@ -338,4 +340,9 @@ abstract class ComponentDependenciesModule { @IntoMap @ComponentDependenciesKey(GlobalSearchDependencies::class) abstract fun provideGlobalSearchDependencies(component: MainComponent): ComponentDependencies + + @Binds + @IntoMap + @ComponentDependenciesKey(MembershipUpdateComponentDependencies::class) + abstract fun provideMembershipUpdateComponentDependencies(component: MainComponent): ComponentDependencies } \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/ui/multiplayer/SpaceJoinRequestFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/multiplayer/SpaceJoinRequestFragment.kt index 84fa3aedff..e766746d62 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/multiplayer/SpaceJoinRequestFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/multiplayer/SpaceJoinRequestFragment.kt @@ -23,6 +23,7 @@ import com.anytypeio.anytype.di.common.componentManager import com.anytypeio.anytype.presentation.multiplayer.SpaceJoinRequestViewModel import com.anytypeio.anytype.ui.settings.typography import javax.inject.Inject +import timber.log.Timber class SpaceJoinRequestFragment : BaseBottomSheetComposeFragment() { @@ -69,17 +70,19 @@ class SpaceJoinRequestFragment : BaseBottomSheetComposeFragment() { } } - private fun proceedWithCommand(command: SpaceJoinRequestViewModel.Command?) { - when(command) { + private fun proceedWithCommand(command: SpaceJoinRequestViewModel.Command) { + Timber.d("proceedWithCommand: $command") + when (command) { SpaceJoinRequestViewModel.Command.NavigateToMembership -> { findNavController().navigate(R.id.paymentsScreen) } - null -> { - // Do nothing. + SpaceJoinRequestViewModel.Command.NavigateToMembershipUpdate -> { + findNavController().navigate(R.id.membershipUpdateScreen) } } } + override fun injectDependencies() { componentManager().spaceJoinRequestComponent.get( SpaceJoinRequestViewModel.VmParams( diff --git a/app/src/main/java/com/anytypeio/anytype/ui/payments/MembershipUpgradeFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/payments/MembershipUpgradeFragment.kt new file mode 100644 index 0000000000..4869e04fd4 --- /dev/null +++ b/app/src/main/java/com/anytypeio/anytype/ui/payments/MembershipUpgradeFragment.kt @@ -0,0 +1,78 @@ +package com.anytypeio.anytype.ui.payments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.fragment.app.viewModels +import com.anytypeio.anytype.R +import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_ui.common.ComposeDialogView +import com.anytypeio.anytype.core_ui.extensions.color +import com.anytypeio.anytype.core_utils.intents.SystemAction +import com.anytypeio.anytype.core_utils.intents.proceedWithAction +import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetComposeFragment +import com.anytypeio.anytype.di.common.componentManager +import com.anytypeio.anytype.payments.screens.MembershipUpgradeScreen +import com.anytypeio.anytype.presentation.membership.MembershipUpgradeViewModel +import com.google.android.material.bottomsheet.BottomSheetDialog +import javax.inject.Inject + +class MembershipUpgradeFragment : BaseBottomSheetComposeFragment() { + + @Inject + lateinit var factory: MembershipUpgradeViewModel.Factory + private val vm by viewModels { factory } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + return ComposeDialogView(context = requireContext(), dialog = requireDialog()).apply { + dialog?.setOnShowListener { dg -> + val bottomSheet = (dg as? BottomSheetDialog)?.findViewById( + com.google.android.material.R.id.design_bottom_sheet + ) + bottomSheet?.setBackgroundColor(requireContext().color(android.R.color.transparent)) + } + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + MembershipUpgradeScreen( + onDismiss = { }, + onButtonClicked = vm::onContactButtonClicked + ) + LaunchedEffect(Unit) { + vm.commands.collect { command -> + when (command) { + is MembershipUpgradeViewModel.Command.ShowEmail -> { + proceedWithEmailCreate(command.account) + } + } + } + } + } + } + } + + private fun proceedWithEmailCreate(accountId: Id) { + val mail = resources.getString(R.string.payments_email_to) + val subject = resources.getString(R.string.payments_email_subject, accountId) + val body = resources.getString(R.string.payments_email_body) + val mailBody = mail + + "?subject=$subject" + + "&body=$body" + proceedWithAction(SystemAction.MailTo(mailBody)) + } + + override fun injectDependencies() { + componentManager().membershipUpgradeComponent.get().inject(this) + } + + override fun releaseDependencies() { + componentManager().membershipUpgradeComponent.release() + } +} \ No newline at end of file diff --git a/app/src/main/res/navigation/graph.xml b/app/src/main/res/navigation/graph.xml index e2933d4859..00e9460897 100644 --- a/app/src/main/res/navigation/graph.xml +++ b/app/src/main/res/navigation/graph.xml @@ -276,6 +276,10 @@ android:id="@+id/paymentsScreen" android:name="com.anytypeio.anytype.ui.payments.MembershipFragment" /> + + diff --git a/localization/src/main/res/values/strings.xml b/localization/src/main/res/values/strings.xml index 030724d254..e40d411112 100644 --- a/localization/src/main/res/values/strings.xml +++ b/localization/src/main/res/values/strings.xml @@ -1640,11 +1640,15 @@ Please provide specific details of your needs here. You’ve already acquired a Membership plan using another Anytype account. Found a subscription with a different id Found more than one subscription - Space participant's error - Request's member error + Space participant\'s error + Request\'s member error Current user status error Current membership status error + Membership upgrade + Reach us for extra storage, space editors, or more shared spaces. Anytype team will provide details and conditions tailored to your needs. + Contact Anytype Team + year %d years diff --git a/payments/src/main/java/com/anytypeio/anytype/payments/screens/MembershipUpgradeScreen.kt b/payments/src/main/java/com/anytypeio/anytype/payments/screens/MembershipUpgradeScreen.kt new file mode 100644 index 0000000000..3daf429157 --- /dev/null +++ b/payments/src/main/java/com/anytypeio/anytype/payments/screens/MembershipUpgradeScreen.kt @@ -0,0 +1,108 @@ +package com.anytypeio.anytype.payments.screens + +import android.content.res.Configuration +import android.util.Log +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.colorResource +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_ui.foundation.Dragger +import com.anytypeio.anytype.core_ui.views.BodyRegular +import com.anytypeio.anytype.core_ui.views.ButtonPrimary +import com.anytypeio.anytype.core_ui.views.ButtonSize +import com.anytypeio.anytype.core_ui.views.HeadlineHeading +import com.anytypeio.anytype.payments.R + +@Composable +fun MembershipUpgradeScreen( + onButtonClicked: () -> Unit, + onDismiss: () -> Unit +) { + Log.d("MembershipUpgradeScreen", "onButtonClicked: $onButtonClicked, onDismiss: $onDismiss") + ElevatedCard( + modifier = Modifier.padding(20.dp), + colors = CardDefaults.cardColors( + containerColor = colorResource(id = R.color.background_primary) + ), + elevation = CardDefaults.elevatedCardElevation( + defaultElevation = 16.dp + ), + shape = RoundedCornerShape(16.dp) + ) { + Column( + modifier = Modifier + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Box( + modifier = Modifier + .padding(vertical = 6.dp) + .fillMaxWidth(), + contentAlignment = Alignment.Center + ) { + Dragger() + } + Spacer(modifier = Modifier.height(19.dp)) + Text( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp), + text = stringResource(id = R.string.membership_upgrade_title), + color = colorResource(id = R.color.text_primary), + style = HeadlineHeading, + textAlign = TextAlign.Center + ) + Spacer(modifier = Modifier.height(14.dp)) + Text( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp), + text = stringResource(id = R.string.membership_upgrade_description), + color = colorResource(id = R.color.text_primary), + style = BodyRegular, + textAlign = TextAlign.Center + ) + Spacer(modifier = Modifier.height(30.dp)) + ButtonPrimary( + text = stringResource(id = R.string.membership_upgrade_button), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp), + onClick = { onButtonClicked() }, + size = ButtonSize.LargeSecondary + ) + Spacer(modifier = Modifier.height(16.dp)) + } + } +} + + +@Preview( + name = "Dark Mode", + showBackground = true, + uiMode = Configuration.UI_MODE_NIGHT_YES +) +@Preview( + name = "Light Mode", + showBackground = true, + uiMode = Configuration.UI_MODE_NIGHT_NO +) +@Composable +fun MembershipUpgradeScreenPreview() { + MembershipUpgradeScreen(onDismiss = {}, onButtonClicked = {}) +} \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/membership/MembershipUpgradeViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/membership/MembershipUpgradeViewModel.kt new file mode 100644 index 0000000000..e2ce9e1147 --- /dev/null +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/membership/MembershipUpgradeViewModel.kt @@ -0,0 +1,40 @@ +package com.anytypeio.anytype.presentation.membership + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.domain.auth.interactor.GetAccount + +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.launch + +class MembershipUpgradeViewModel( + private val getAccount: GetAccount +) : ViewModel() { + + val commands = MutableSharedFlow(0) + + fun onContactButtonClicked() { + viewModelScope.launch { + val account = getAccount.async(Unit).getOrNull() ?: return@launch + commands.emit(Command.ShowEmail(account.id)) + } + } + + sealed class Command { + data class ShowEmail(val account: Id) : Command() + } + + class Factory @Inject constructor( + private val getAccount: GetAccount, + ) : ViewModelProvider.Factory { + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class): T { + return MembershipUpgradeViewModel( + getAccount = getAccount + ) as T + } + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/multiplayer/SpaceJoinRequestViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/multiplayer/SpaceJoinRequestViewModel.kt index 21ce90dbc3..feb0919d5c 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/multiplayer/SpaceJoinRequestViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/multiplayer/SpaceJoinRequestViewModel.kt @@ -392,7 +392,7 @@ class SpaceJoinRequestViewModel( if (isPossibleToUpgrade) { _commands.emit(Command.NavigateToMembership) } else { - //todo navigate to membership email screen + _commands.emit(Command.NavigateToMembershipUpdate) } } } @@ -476,6 +476,7 @@ class SpaceJoinRequestViewModel( sealed class Command { data object NavigateToMembership : Command() + data object NavigateToMembershipUpdate : Command() } } diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorCreateBlockTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorCreateBlockTest.kt index 5ba560bb7a..521a9dcda8 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorCreateBlockTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorCreateBlockTest.kt @@ -95,7 +95,7 @@ class EditorCreateBlockTest : EditorPresentationTestSetup() { position = Position.BOTTOM, prototype = Block.Prototype.Link( target = linkToObject, - cardStyle = Block.Content.Link.CardStyle.TEXT, + cardStyle = Block.Content.Link.CardStyle.CARD, iconSize = Block.Content.Link.IconSize.SMALL, description = Block.Content.Link.Description.NONE )