diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f094add445..060e6040ef 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -66,6 +66,13 @@ + + + + + + + diff --git a/app/src/main/java/com/anytypeio/anytype/other/Deeplinks.kt b/app/src/main/java/com/anytypeio/anytype/other/Deeplinks.kt index 059bd7471e..2fbd504ebb 100644 --- a/app/src/main/java/com/anytypeio/anytype/other/Deeplinks.kt +++ b/app/src/main/java/com/anytypeio/anytype/other/Deeplinks.kt @@ -20,12 +20,14 @@ const val MAIN_PATH = "main" const val OBJECT_PATH = "object" const val IMPORT_PATH = "import" const val INVITE_PATH = "invite" +const val MEMBERSHIP_PATH = "membership" const val TYPE_PARAM = "type" const val OBJECT_ID_PARAM = "objectId" const val SPACE_ID_PARAM = "spaceId" const val SOURCE_PARAM = "source" const val TYPE_VALUE_EXPERIENCE = "experience" +const val TIER_ID_PARAM = "tier" const val IMPORT_EXPERIENCE_DEEPLINK = "$DEEP_LINK_PATTERN$MAIN_PATH/$IMPORT_PATH/?$TYPE_PARAM=$TYPE_VALUE_EXPERIENCE" @@ -67,6 +69,12 @@ object DefaultDeepLinkResolver : DeepLinkResolver { DeepLinkResolver.Action.Unknown } } + deeplink.contains(MEMBERSHIP_PATH) -> { + val uri = Uri.parse(deeplink) + DeepLinkResolver.Action.DeepLinkToMembership( + tierId = uri.getQueryParameter(TIER_ID_PARAM) + ) + } else -> DeepLinkResolver.Action.Unknown } diff --git a/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreenFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreenFragment.kt index 59f0f18151..16a8502686 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreenFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreenFragment.kt @@ -16,6 +16,7 @@ import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle +import androidx.navigation.NavOptions import androidx.navigation.fragment.findNavController import com.anytypeio.anytype.R import com.anytypeio.anytype.core_models.Id @@ -36,6 +37,7 @@ import com.anytypeio.anytype.ui.gallery.GalleryInstallationFragment import com.anytypeio.anytype.ui.multiplayer.RequestJoinSpaceFragment import com.anytypeio.anytype.ui.multiplayer.ShareSpaceFragment import com.anytypeio.anytype.ui.objects.creation.SelectObjectTypeFragment +import com.anytypeio.anytype.ui.payments.MembershipFragment import com.anytypeio.anytype.ui.settings.space.SpaceSettingsFragment import com.anytypeio.anytype.ui.settings.typography import com.anytypeio.anytype.ui.widgets.SelectWidgetSourceFragment @@ -245,6 +247,13 @@ class HomeScreenFragment : BaseComposeFragment() { ) ) } + is Command.Deeplink.MembershipScreen -> { + findNavController().navigate( + R.id.paymentsScreen, + MembershipFragment.args(command.tierId), + NavOptions.Builder().setLaunchSingleTop(true).build() + ) + } is Command.Deeplink.DeepLinkToObjectNotWorking -> { toast( getString(R.string.multiplayer_deeplink_to_your_object_error) diff --git a/app/src/main/java/com/anytypeio/anytype/ui/main/MainActivity.kt b/app/src/main/java/com/anytypeio/anytype/ui/main/MainActivity.kt index fb0b89b1fb..9c267a2cca 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/main/MainActivity.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/main/MainActivity.kt @@ -55,6 +55,7 @@ import com.anytypeio.anytype.ui.multiplayer.RequestJoinSpaceFragment import com.anytypeio.anytype.ui.multiplayer.ShareSpaceFragment import com.anytypeio.anytype.ui.multiplayer.SpaceJoinRequestFragment import com.anytypeio.anytype.ui.notifications.NotificationsFragment +import com.anytypeio.anytype.ui.payments.MembershipFragment import com.anytypeio.anytype.ui.sets.ObjectSetFragment import com.anytypeio.anytype.ui.sharing.SharingFragment import com.anytypeio.anytype.ui_settings.appearance.ThemeApplicator @@ -251,6 +252,17 @@ class MainActivity : AppCompatActivity(R.layout.activity_main), AppNavigation.Pr Timber.e(it, "Error while navigation for deep link invite") } } + is Command.Deeplink.MembershipScreen -> { + runCatching { + findNavController(R.id.fragment).navigate( + R.id.paymentsScreen, + MembershipFragment.args(tierId = command.tierId), + NavOptions.Builder().setLaunchSingleTop(true).build() + ) + }.onFailure { + Timber.w(it, "Error while navigation for deep link membership tier") + } + } } } } diff --git a/app/src/main/java/com/anytypeio/anytype/ui/payments/MembershipFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/payments/MembershipFragment.kt index c52018e3c3..8c869887c5 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/payments/MembershipFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/payments/MembershipFragment.kt @@ -12,6 +12,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.res.stringResource +import androidx.core.os.bundleOf import androidx.fragment.app.viewModels import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.lifecycleScope @@ -23,6 +24,7 @@ import com.anytypeio.anytype.BuildConfig import com.anytypeio.anytype.R import com.anytypeio.anytype.core_ui.common.ComposeDialogView import com.anytypeio.anytype.core_ui.views.BaseTwoButtonsDarkThemeAlertDialog +import com.anytypeio.anytype.core_utils.ext.argOrNull import com.anytypeio.anytype.core_utils.ext.setupBottomSheetBehavior import com.anytypeio.anytype.core_utils.ext.subscribe import com.anytypeio.anytype.core_utils.ext.toast @@ -59,6 +61,8 @@ class MembershipFragment : BaseBottomSheetComposeFragment() { private val vm by viewModels { factory } private lateinit var navController: NavHostController + private val argTierId get() = argOrNull(ARG_TIER_ID) + @Inject lateinit var billingClientLifecycle: BillingClientLifecycle @@ -183,6 +187,7 @@ class MembershipFragment : BaseBottomSheetComposeFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + vm.showTierOnStart(tierId = argTierId) setupBottomSheetBehavior(DEFAULT_PADDING_TOP) subscribe(vm.navigation) { command -> Timber.d("MembershipFragment command: $command") @@ -209,10 +214,12 @@ class MembershipFragment : BaseBottomSheetComposeFragment() { toast("Couldn't parse url: ${command.url}") } } + MembershipNavigation.Main -> {} is MembershipNavigation.OpenEmail -> { val mail = resources.getString(R.string.payments_email_to) - val subject = resources.getString(R.string.payments_email_subject, command.accountId) + val subject = + resources.getString(R.string.payments_email_subject, command.accountId) val body = resources.getString(R.string.payments_email_body) val mailBody = mail + "?subject=$subject" + @@ -228,7 +235,8 @@ class MembershipFragment : BaseBottomSheetComposeFragment() { val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) currentDateTime = sdf.format(Date()) val mail = resources.getString(R.string.membership_support_email) - val subject = resources.getString(R.string.membership_support_subject, command.accountId) + val subject = + resources.getString(R.string.membership_support_subject, command.accountId) val body = getString( R.string.membership_support_body, command.error, currentDateTime, deviceModel, osVersion, appVersion @@ -260,4 +268,9 @@ class MembershipFragment : BaseBottomSheetComposeFragment() { override fun releaseDependencies() { componentManager().membershipComponent.release() } + + companion object { + const val ARG_TIER_ID = "args.membership.tier" + fun args(tierId: String?) = bundleOf(ARG_TIER_ID to tierId) + } } \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/misc/DeepLinkResolver.kt b/domain/src/main/java/com/anytypeio/anytype/domain/misc/DeepLinkResolver.kt index 3618a98a36..7e1cd90f7c 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/misc/DeepLinkResolver.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/misc/DeepLinkResolver.kt @@ -2,6 +2,7 @@ package com.anytypeio.anytype.domain.misc import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.Url +import com.anytypeio.anytype.core_models.membership.TierId import com.anytypeio.anytype.core_models.primitives.SpaceId interface DeepLinkResolver { @@ -22,5 +23,8 @@ interface DeepLinkResolver { val obj: Id, val space: SpaceId ) : Action() + data class DeepLinkToMembership( + val tierId: String? + ) : Action() } } \ No newline at end of file diff --git a/payments/src/main/java/com/anytypeio/anytype/payments/viewmodel/MembershipViewModel.kt b/payments/src/main/java/com/anytypeio/anytype/payments/viewmodel/MembershipViewModel.kt index 87d4a0b753..f7679913e0 100644 --- a/payments/src/main/java/com/anytypeio/anytype/payments/viewmodel/MembershipViewModel.kt +++ b/payments/src/main/java/com/anytypeio/anytype/payments/viewmodel/MembershipViewModel.kt @@ -22,6 +22,7 @@ import com.anytypeio.anytype.domain.payments.SetMembershipEmail import com.anytypeio.anytype.domain.payments.VerifyMembershipEmailCode 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.core_models.membership.MembershipConstants.NONE_ID import com.anytypeio.anytype.payments.mapping.toMainView import com.anytypeio.anytype.payments.models.MembershipPurchase import com.anytypeio.anytype.payments.models.TierAnyName @@ -46,6 +47,8 @@ import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import timber.log.Timber @@ -71,6 +74,8 @@ class MembershipViewModel( val navigation = MutableSharedFlow(0) + private val _showTierOnStart = MutableStateFlow(NONE_ID) + /** * Local billing purchase data. */ @@ -93,6 +98,17 @@ class MembershipViewModel( val anyEmailState = TextFieldState(initialText = "") init { + Timber.i("MembershipViewModel, init") + viewModelScope.launch { + combine( + _showTierOnStart.filter { it != NONE_ID }, + viewState.filterIsInstance() + ) { tierId, _ -> tierId }.collect { tierId -> + Timber.d("_showTierOnStart, get new value:$tierId") + proceedWithShowingTier(TierId(tierId)) + } + } + viewModelScope.launch { val account = getAccount.async(Unit) val accountId = account.getOrNull()?.id.orEmpty() @@ -131,6 +147,13 @@ class MembershipViewModel( } } + fun showTierOnStart(tierId: String?) { + val tier = tierId?.toIntOrNull() + if (tier != null && tier != NONE_ID) { + _showTierOnStart.value = tier + } + } + private fun proceedWithUpdatingVisibleTier(mainState: MembershipMainState) { val actualTier = tierState.value if (actualTier is MembershipTierState.Visible && mainState is MembershipMainState.Default) { diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModel.kt index e5e99685fe..d0b29a5ed6 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModel.kt @@ -1272,6 +1272,15 @@ class HomeScreenViewModel( } } } + is DeepLinkResolver.Action.DeepLinkToMembership -> { + viewModelScope.launch { + commands.emit( + Command.Deeplink.MembershipScreen( + tierId = deeplink.tierId + ) + ) + } + } else -> { Timber.d("No deep link") } @@ -2190,6 +2199,7 @@ sealed class Command { val deepLinkType: String, val deepLinkSource: String ) : Deeplink() + data class MembershipScreen(val tierId: String?) : Deeplink() } } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/main/MainViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/main/MainViewModel.kt index 4032673446..28ce749e55 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/main/MainViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/main/MainViewModel.kt @@ -12,6 +12,7 @@ import com.anytypeio.anytype.core_models.NotificationPayload import com.anytypeio.anytype.core_models.NotificationStatus import com.anytypeio.anytype.core_models.Wallpaper import com.anytypeio.anytype.core_models.exceptions.NeedToUpdateApplicationException +import com.anytypeio.anytype.core_models.membership.TierId import com.anytypeio.anytype.core_utils.ext.cancel import com.anytypeio.anytype.domain.account.AwaitAccountStartManager import com.anytypeio.anytype.domain.account.InterceptAccountStatus @@ -350,6 +351,11 @@ class MainViewModel( } } } + is DeepLinkResolver.Action.DeepLinkToMembership -> { + commands.emit( + Command.Deeplink.MembershipScreen(tierId = deeplink.tierId) + ) + } else -> { Timber.d("No deep link") } @@ -381,6 +387,7 @@ class MainViewModel( val deepLinkType: String, val deepLinkSource: String ) : Deeplink() + data class MembershipScreen(val tierId: String?) : Deeplink() } }