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 956ced5904..5bdf4e1fba 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 @@ -1,7 +1,6 @@ package com.anytypeio.anytype.di.feature import com.anytypeio.anytype.analytics.base.Analytics -import com.anytypeio.anytype.core_utils.di.scope.PerDialog import com.anytypeio.anytype.core_utils.di.scope.PerScreen import com.anytypeio.anytype.domain.account.AccountStatusChannel import com.anytypeio.anytype.domain.account.AwaitAccountStartManager @@ -13,6 +12,7 @@ import com.anytypeio.anytype.domain.auth.repo.AuthRepository import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers import com.anytypeio.anytype.domain.config.ConfigStorage import com.anytypeio.anytype.domain.config.UserSettingsRepository +import com.anytypeio.anytype.domain.deeplink.PendingIntentStore import com.anytypeio.anytype.domain.device.PathProvider import com.anytypeio.anytype.domain.misc.LocaleProvider import com.anytypeio.anytype.domain.multiplayer.SpaceInviteResolver @@ -77,7 +77,8 @@ object MainEntryModule { globalSubscriptionManager: GlobalSubscriptionManager, spaceInviteResolver: SpaceInviteResolver, spaceManager: SpaceManager, - spaceViewSubscriptionContainer: SpaceViewSubscriptionContainer + spaceViewSubscriptionContainer: SpaceViewSubscriptionContainer, + pendingIntentStore: PendingIntentStore ): MainViewModelFactory = MainViewModelFactory( resumeAccount = resumeAccount, analytics = analytics, @@ -97,7 +98,8 @@ object MainEntryModule { globalSubscriptionManager = globalSubscriptionManager, spaceInviteResolver = spaceInviteResolver, spaceManager = spaceManager, - spaceViews = spaceViewSubscriptionContainer + spaceViews = spaceViewSubscriptionContainer, + pendingIntentStore = pendingIntentStore ) @JvmStatic diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/ObjectMenuDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/ObjectMenuDI.kt index 062355949d..f8b0ea0b5c 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/ObjectMenuDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/ObjectMenuDI.kt @@ -30,7 +30,6 @@ import com.anytypeio.anytype.domain.relations.RemoveFromFeaturedRelations import com.anytypeio.anytype.domain.templates.CreateTemplateFromObject import com.anytypeio.anytype.domain.widgets.CreateWidget import com.anytypeio.anytype.domain.workspace.SpaceManager -import com.anytypeio.anytype.other.DefaultDeepLinkResolver import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate import com.anytypeio.anytype.presentation.common.Action import com.anytypeio.anytype.presentation.common.Delegator @@ -223,11 +222,6 @@ object ObjectMenuModule { dispatchers = dispatchers ) - @JvmStatic - @Provides - @PerDialog - fun provideDeeplinkResolver() : DeepLinkResolver = DefaultDeepLinkResolver - @JvmStatic @Provides @PerDialog @@ -377,11 +371,6 @@ object ObjectSetMenuModule { dispatchers = dispatchers ) - @JvmStatic - @Provides - @PerDialog - fun provideDeeplinkResolver() : DeepLinkResolver = DefaultDeepLinkResolver - @JvmStatic @Provides @PerDialog diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/onboarding/signup/OnboardingMnemonicDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/onboarding/signup/OnboardingMnemonicDI.kt index ade1527b09..b6f5ab156b 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/onboarding/signup/OnboardingMnemonicDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/onboarding/signup/OnboardingMnemonicDI.kt @@ -6,6 +6,7 @@ import com.anytypeio.anytype.di.common.ComponentDependencies import com.anytypeio.anytype.domain.auth.interactor.GetMnemonic import com.anytypeio.anytype.domain.auth.repo.AuthRepository import com.anytypeio.anytype.domain.config.ConfigStorage +import com.anytypeio.anytype.domain.deeplink.PendingIntentStore import com.anytypeio.anytype.domain.device.NetworkConnectionStatus import com.anytypeio.anytype.domain.network.NetworkModeProvider import com.anytypeio.anytype.presentation.onboarding.signup.OnboardingMnemonicViewModel @@ -61,6 +62,7 @@ interface OnboardingMnemonicDependencies : ComponentDependencies { fun config(): ConfigStorage fun networkModeProvider(): NetworkModeProvider fun networkConnectionStatus(): NetworkConnectionStatus + fun pendingIntentStore(): PendingIntentStore } @Scope diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/onboarding/signup/OnboardingSoulCreationDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/onboarding/signup/OnboardingSoulCreationDI.kt index 8411126173..381fee815e 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/onboarding/signup/OnboardingSoulCreationDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/onboarding/signup/OnboardingSoulCreationDI.kt @@ -3,7 +3,6 @@ package com.anytypeio.anytype.di.feature.onboarding.signup import androidx.lifecycle.ViewModelProvider import com.anytypeio.anytype.CrashReporter import com.anytypeio.anytype.analytics.base.Analytics -import com.anytypeio.anytype.core_utils.di.scope.PerScreen import com.anytypeio.anytype.di.common.ComponentDependencies import com.anytypeio.anytype.domain.account.AwaitAccountStartManager import com.anytypeio.anytype.domain.auth.interactor.CreateAccount @@ -13,6 +12,7 @@ import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers import com.anytypeio.anytype.domain.block.repo.BlockRepository import com.anytypeio.anytype.domain.config.ConfigStorage import com.anytypeio.anytype.domain.config.UserSettingsRepository +import com.anytypeio.anytype.domain.deeplink.PendingIntentStore import com.anytypeio.anytype.domain.device.PathProvider import com.anytypeio.anytype.domain.misc.LocaleProvider import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider @@ -131,6 +131,7 @@ interface OnboardingSoulCreationDependencies : ComponentDependencies { fun awaitAccountStartManager(): AwaitAccountStartManager fun globalSubscriptionManager(): GlobalSubscriptionManager fun stringResourceProvider(): StringResourceProvider + fun providePendingIntentStore(): PendingIntentStore } @Scope diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/vault/VaultDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/vault/VaultDI.kt index 99457479bb..8d41c7af91 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/vault/VaultDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/vault/VaultDI.kt @@ -2,7 +2,6 @@ package com.anytypeio.anytype.di.feature.vault import androidx.lifecycle.ViewModelProvider import com.anytypeio.anytype.analytics.base.Analytics -import com.anytypeio.anytype.core_utils.di.scope.PerDialog import com.anytypeio.anytype.core_utils.di.scope.PerScreen import com.anytypeio.anytype.di.common.ComponentDependencies import com.anytypeio.anytype.domain.account.AwaitAccountStartManager @@ -12,6 +11,7 @@ import com.anytypeio.anytype.domain.block.repo.BlockRepository import com.anytypeio.anytype.domain.chats.ChatPreviewContainer import com.anytypeio.anytype.domain.config.UserSettingsRepository import com.anytypeio.anytype.domain.debugging.Logger +import com.anytypeio.anytype.domain.deeplink.PendingIntentStore import com.anytypeio.anytype.domain.misc.AppActionManager import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.multiplayer.SpaceInviteResolver @@ -83,4 +83,5 @@ interface VaultComponentDependencies : ComponentDependencies { fun awaitAccount(): AwaitAccountStartManager fun profileContainer(): ProfileSubscriptionManager fun chatPreviewContainer(): ChatPreviewContainer + fun pendingIntentStore(): PendingIntentStore } \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/di/main/SubscriptionsModule.kt b/app/src/main/java/com/anytypeio/anytype/di/main/SubscriptionsModule.kt index 89abdb5de1..1379e63b74 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/main/SubscriptionsModule.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/main/SubscriptionsModule.kt @@ -13,10 +13,12 @@ import com.anytypeio.anytype.domain.chats.ChatPreviewContainer import com.anytypeio.anytype.domain.config.ConfigStorage import com.anytypeio.anytype.domain.debugging.DebugAccountSelectTrace import com.anytypeio.anytype.domain.debugging.Logger +import com.anytypeio.anytype.domain.deeplink.PendingIntentStore import com.anytypeio.anytype.domain.device.NetworkConnectionStatus import com.anytypeio.anytype.domain.device.DeviceTokenStoringService import com.anytypeio.anytype.domain.event.interactor.SpaceSyncAndP2PStatusProvider import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer +import com.anytypeio.anytype.domain.misc.DeepLinkResolver import com.anytypeio.anytype.domain.multiplayer.ActiveSpaceMemberSubscriptionContainer import com.anytypeio.anytype.domain.multiplayer.DefaultUserPermissionProvider import com.anytypeio.anytype.domain.multiplayer.SpaceViewSubscriptionContainer @@ -37,6 +39,7 @@ import com.anytypeio.anytype.domain.spaces.SpaceDeletedStatusWatcher import com.anytypeio.anytype.domain.subscriptions.GlobalSubscriptionManager import com.anytypeio.anytype.domain.workspace.SpaceManager import com.anytypeio.anytype.domain.workspace.SyncAndP2PStatusChannel +import com.anytypeio.anytype.other.DefaultDeepLinkResolver import com.anytypeio.anytype.presentation.sync.SpaceSyncAndP2PStatusProviderImpl import dagger.Module import dagger.Provides @@ -298,4 +301,14 @@ object SubscriptionsModule { dispatchers = dispatchers, scope = scope ) + + @JvmStatic + @Provides + @Singleton + fun provideDeeplinkResolver() : DeepLinkResolver = DefaultDeepLinkResolver + + @JvmStatic + @Provides + @Singleton + fun providePendingIntentStore(): PendingIntentStore = PendingIntentStore() } \ No newline at end of file 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 48e44bda13..ca0f20002c 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 @@ -455,7 +455,7 @@ class MainActivity : AppCompatActivity(R.layout.activity_main), AppNavigation.Pr intent.data?.let { uri -> val data = uri.toString() if (DefaultDeepLinkResolver.isDeepLink(data)) { - vm.onNewDeepLink(DefaultDeepLinkResolver.resolve(data)) + vm.handleNewDeepLink(DefaultDeepLinkResolver.resolve(data)) // Optionally clear to prevent repeat intent.action = null @@ -544,7 +544,7 @@ class MainActivity : AppCompatActivity(R.layout.activity_main), AppNavigation.Pr when { checkDeepLink && DefaultDeepLinkResolver.isDeepLink(raw) -> { - vm.onNewDeepLink(DefaultDeepLinkResolver.resolve(raw)) + vm.handleNewDeepLink(DefaultDeepLinkResolver.resolve(raw)) } raw.isNotEmpty() && !DefaultDeepLinkResolver.isDeepLink(raw) -> { vm.onIntentTextShare(raw) diff --git a/app/src/main/java/com/anytypeio/anytype/ui/multiplayer/ShareSpaceFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/multiplayer/ShareSpaceFragment.kt index 1f378f67b2..030c390e53 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/multiplayer/ShareSpaceFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/multiplayer/ShareSpaceFragment.kt @@ -145,7 +145,7 @@ class ShareSpaceFragment : BaseBottomSheetComposeFragment() { } dialog.show(childFragmentManager, null) }.onFailure { - Timber.e(it, "Error while navigation") + Timber.e(it, "Error while showing remove member warning") } } is Command.ShowStopSharingWarning -> { diff --git a/app/src/main/java/com/anytypeio/anytype/ui/onboarding/screens/signup/OnboardingMnemonicPhraseScreen.kt b/app/src/main/java/com/anytypeio/anytype/ui/onboarding/screens/signup/OnboardingMnemonicPhraseScreen.kt index 98df37835b..e7c996a984 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/onboarding/screens/signup/OnboardingMnemonicPhraseScreen.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/onboarding/screens/signup/OnboardingMnemonicPhraseScreen.kt @@ -78,7 +78,7 @@ fun MnemonicPhraseScreenWrapper( copyMnemonicToClipboard = copyMnemonicToClipboard, mnemonicColorPalette = mnemonicColorPalette, onGoToAppClicked = { - vm.onGoToTheAppClicked( + vm.handleAppEntryClick( space = space, startingObject = startingObject ) diff --git a/app/src/main/java/com/anytypeio/anytype/ui/vault/VaultFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/vault/VaultFragment.kt index 4c8d246127..e3f50ef113 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/vault/VaultFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/vault/VaultFragment.kt @@ -14,7 +14,6 @@ import androidx.fragment.app.viewModels import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavOptions import androidx.navigation.fragment.findNavController -import com.anytypeio.anytype.BuildConfig.USE_EDGE_TO_EDGE import com.anytypeio.anytype.R import com.anytypeio.anytype.core_utils.ext.argOrNull import com.anytypeio.anytype.core_utils.ext.toast @@ -245,6 +244,7 @@ class VaultFragment : BaseComposeFragment() { override fun onResume() { super.onResume() proceedWithDeepLinks() + vm.processPendingDeeplink() } private fun proceedWithDeepLinks() { diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/deeplink/PendingIntentStore.kt b/domain/src/main/java/com/anytypeio/anytype/domain/deeplink/PendingIntentStore.kt new file mode 100644 index 0000000000..18f7423ae4 --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/deeplink/PendingIntentStore.kt @@ -0,0 +1,23 @@ +package com.anytypeio.anytype.domain.deeplink + +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Store for keeping pending invite deeplinks in memory. + * This is used to handle invite deeplinks that were received while user was not logged in. + */ +@Singleton +class PendingIntentStore @Inject constructor() { + private var deepLinkInvite: String? = null + + fun setDeepLinkInvite(link: String?) { + deepLinkInvite = link + } + + fun getDeepLinkInvite(): String? = deepLinkInvite + + fun clearDeepLinkInvite() { + deepLinkInvite = null + } +} \ No newline at end of file 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 9ea02691c4..2bfa5e1ed1 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 @@ -23,6 +23,7 @@ import com.anytypeio.anytype.domain.auth.model.AuthStatus import com.anytypeio.anytype.domain.base.BaseUseCase import com.anytypeio.anytype.domain.base.Interactor import com.anytypeio.anytype.domain.config.ConfigStorage +import com.anytypeio.anytype.domain.deeplink.PendingIntentStore import com.anytypeio.anytype.domain.misc.DeepLinkResolver import com.anytypeio.anytype.domain.misc.LocaleProvider import com.anytypeio.anytype.domain.multiplayer.SpaceInviteResolver @@ -69,7 +70,8 @@ class MainViewModel( private val globalSubscriptionManager: GlobalSubscriptionManager, private val spaceInviteResolver: SpaceInviteResolver, private val spaceManager: SpaceManager, - private val spaceViews: SpaceViewSubscriptionContainer + private val spaceViews: SpaceViewSubscriptionContainer, + private val pendingIntentStore: PendingIntentStore ) : ViewModel(), NotificationActionDelegate by notificationActionDelegate, DeepLinkToObjectDelegate by deepLinkToObjectDelegate { @@ -308,8 +310,34 @@ class MainViewModel( } } - fun onNewDeepLink(deeplink: DeepLinkResolver.Action) { + fun handleNewDeepLink(deeplink: DeepLinkResolver.Action) { deepLinkJobs.cancel() + viewModelScope.launch { + checkAuthorizationStatus(Unit).process( + failure = { Timber.e(it, "Failed to check authentication status") }, + success = { authStatus -> processDeepLinkBasedOnAuth(authStatus, deeplink) } + ) + } + } + + private fun processDeepLinkBasedOnAuth( + authStatus: AuthStatus, + deeplink: DeepLinkResolver.Action + ) { + if (authStatus == AuthStatus.UNAUTHORIZED && deeplink is DeepLinkResolver.Action.Invite) { + saveInviteDeepLinkForLater(deeplink) + } else { + Timber.d("Proceeding with deeplink: $deeplink") + launchDeepLinkProcessing(deeplink) + } + } + + private fun saveInviteDeepLinkForLater(deeplink: DeepLinkResolver.Action.Invite) { + pendingIntentStore.setDeepLinkInvite(deeplink.link) + Timber.d("Saved invite deeplink for later processing: ${deeplink.link}") + } + + private fun launchDeepLinkProcessing(deeplink: DeepLinkResolver.Action) { deepLinkJobs += viewModelScope.launch { awaitAccountStartManager .awaitStart() 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 cc92de847e..5f1deb90c1 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 @@ -9,6 +9,7 @@ import com.anytypeio.anytype.domain.auth.interactor.CheckAuthorizationStatus import com.anytypeio.anytype.domain.auth.interactor.Logout import com.anytypeio.anytype.domain.auth.interactor.ResumeAccount import com.anytypeio.anytype.domain.config.ConfigStorage +import com.anytypeio.anytype.domain.deeplink.PendingIntentStore import com.anytypeio.anytype.domain.misc.LocaleProvider import com.anytypeio.anytype.domain.multiplayer.SpaceInviteResolver import com.anytypeio.anytype.domain.multiplayer.SpaceViewSubscriptionContainer @@ -42,7 +43,8 @@ class MainViewModelFactory @Inject constructor( private val globalSubscriptionManager: GlobalSubscriptionManager, private val spaceInviteResolver: SpaceInviteResolver, private val spaceManager: SpaceManager, - private val spaceViews: SpaceViewSubscriptionContainer + private val spaceViews: SpaceViewSubscriptionContainer, + private val pendingIntentStore: PendingIntentStore ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create( @@ -66,6 +68,7 @@ class MainViewModelFactory @Inject constructor( globalSubscriptionManager = globalSubscriptionManager, spaceInviteResolver = spaceInviteResolver, spaceManager = spaceManager, - spaceViews = spaceViews + spaceViews = spaceViews, + pendingIntentStore = pendingIntentStore ) as T } \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/multiplayer/ShareSpaceViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/multiplayer/ShareSpaceViewModel.kt index 1a94beab7a..bfb3c8005f 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/multiplayer/ShareSpaceViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/multiplayer/ShareSpaceViewModel.kt @@ -416,23 +416,39 @@ class ShareSpaceViewModel( } fun onRemoveMemberAccepted(identity: Id) { - Timber.d("onRemoveMemberAccepted") + Timber.d("onRemoveMemberAccepted: Starting member removal process for identity: $identity") viewModelScope.launch { - removeSpaceMembers.async( - RemoveSpaceMembers.Params( - space = vmParams.space, - identities = listOf(identity) + try { + removeSpaceMembers.async( + RemoveSpaceMembers.Params( + space = vmParams.space, + identities = listOf(identity) + ) + ).fold( + onFailure = { e -> + Timber.e( + e, + "Error while removing space member (identity: $identity, space: ${vmParams.space})" + ) + when (e) { + is java.net.SocketTimeoutException, + is java.net.UnknownHostException, + is java.io.IOException -> { + sendToast("Network error occurred. Please check your connection and try again.") + } + + else -> proceedWithMultiplayerError(e) + } + }, + onSuccess = { + Timber.d("Successfully removed space member (identity: $identity, space: ${vmParams.space})") + analytics.sendEvent(eventName = removeSpaceMember) + } ) - ).fold( - onFailure = { e -> - Timber.e(e, "Error while removing space member") - proceedWithMultiplayerError(e) - }, - onSuccess = { - Timber.d("Successfully removed space member") - analytics.sendEvent(eventName = removeSpaceMember) - } - ) + } catch (e: Exception) { + Timber.e(e, "Unexpected error while removing space member") + sendToast("An unexpected error occurred. Please try again.") + } } } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/onboarding/signup/OnboardingMnemonicViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/onboarding/signup/OnboardingMnemonicViewModel.kt index 17a7ea06f4..d03ddfa5d5 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/onboarding/signup/OnboardingMnemonicViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/onboarding/signup/OnboardingMnemonicViewModel.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import com.anytypeio.anytype.analytics.base.Analytics import com.anytypeio.anytype.analytics.base.EventsDictionary +import com.anytypeio.anytype.analytics.base.EventsDictionary.ClickOnboardingButton import com.anytypeio.anytype.core_models.DeviceNetworkType import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.NetworkMode @@ -16,6 +17,7 @@ import com.anytypeio.anytype.domain.network.NetworkModeProvider import com.anytypeio.anytype.presentation.extension.sendAnalyticsOnboardingClickEvent import com.anytypeio.anytype.presentation.extension.sendAnalyticsOnboardingScreenEvent import com.anytypeio.anytype.presentation.extension.sendOpenAccountEvent +import com.anytypeio.anytype.domain.deeplink.PendingIntentStore import javax.inject.Inject import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -27,7 +29,8 @@ class OnboardingMnemonicViewModel @Inject constructor( private val analytics: Analytics, private val configStorage: ConfigStorage, private val networkModeProvider: NetworkModeProvider, - private val networkConnectionStatus: NetworkConnectionStatus + private val networkConnectionStatus: NetworkConnectionStatus, + private val pendingIntentStore: PendingIntentStore ) : ViewModel() { val state = MutableStateFlow(State.Idle("")) @@ -35,9 +38,7 @@ class OnboardingMnemonicViewModel @Inject constructor( init { Timber.i("OnboardingMnemonicViewModel, init") - viewModelScope.sendAnalyticsOnboardingScreenEvent(analytics, - EventsDictionary.ScreenOnboardingStep.PHRASE - ) + sendScreenViewAnalytics() viewModelScope.launch { proceedWithMnemonicPhrase() } @@ -47,92 +48,73 @@ class OnboardingMnemonicViewModel @Inject constructor( if (state.value is State.Mnemonic) { state.value = State.MnemonicOpened((state.value as State.Mnemonic).mnemonicPhrase) } - viewModelScope.sendAnalyticsOnboardingClickEvent( - analytics = analytics, - type = EventsDictionary.ClickOnboardingButton.SHOW_AND_COPY, - step = EventsDictionary.ScreenOnboardingStep.PHRASE - ) + sendClickAnalytics(ClickOnboardingButton.SHOW_AND_COPY) } - fun onCheckLaterClicked( - space: Id, - startingObject: Id?, - ) { - viewModelScope.sendAnalyticsOnboardingClickEvent( - analytics = analytics, - type = EventsDictionary.ClickOnboardingButton.CHECK_LATER, - step = EventsDictionary.ScreenOnboardingStep.PHRASE - ) - if (shouldShowEmail()) { - viewModelScope.launch { - commands.emit( - Command.NavigateToAddEmailScreen( - startingObject = startingObject, - space = space - ) - ) - } - } else { - viewModelScope.launch { - val config = configStorage.getOrNull() - if (config != null) { - analytics.sendOpenAccountEvent( - analytics = config.analytics - ) - } else { - Timber.w("config was missing before the end of onboarding") - } - if (!startingObject.isNullOrEmpty()) { - commands.emit( - Command.OpenStartingObject( - space = SpaceId(space), - startingObject = startingObject - ) - ) - } else { - commands.emit(Command.OpenVault) - } - } + fun onCheckLaterClicked(space: Id, startingObject: Id?) { + sendClickAnalytics(ClickOnboardingButton.CHECK_LATER) + viewModelScope.launch { + navigateNextStep(space, startingObject) } } - fun onGoToTheAppClicked( - space: Id, - startingObject: Id?, - ) { - if (shouldShowEmail()) { - viewModelScope.launch { - commands.emit( - Command.NavigateToAddEmailScreen( - startingObject = startingObject, - space = space - ) - ) - } - } else { - viewModelScope.launch { - val config = configStorage.getOrNull() - if (config != null) { - analytics.sendOpenAccountEvent( - analytics = config.analytics - ) - } else { - Timber.w("config was missing before the end of onboarding") - } - if (!startingObject.isNullOrEmpty()) { - commands.emit( - Command.OpenStartingObject( - space = SpaceId(space), - startingObject = startingObject - ) - ) - } else { - commands.emit(Command.OpenVault) - } - } + fun handleAppEntryClick(space: Id, startingObject: Id?) { + viewModelScope.launch { + navigateNextStep(space, startingObject) } } + private suspend fun navigateNextStep(space: Id, startingObject: Id?) { + if (shouldShowEmail()) { + emitNavigateToAddEmail(space, startingObject) + return + } + + logOpenAccountIfAvailable() + + val deeplink = pendingIntentStore.getDeepLinkInvite() + when { + !deeplink.isNullOrEmpty() -> emitCommand(Command.OpenVault) + !startingObject.isNullOrEmpty() -> emitCommand( + Command.OpenStartingObject( + space = SpaceId(space), + startingObject = startingObject + ) + ) + + else -> emitCommand(Command.OpenVault) + } + } + + private suspend fun emitNavigateToAddEmail(space: Id, startingObject: Id?) { + emitCommand( + Command.NavigateToAddEmailScreen( + space = space, + startingObject = startingObject + ) + ) + } + + private suspend fun emitCommand(command: Command) { + commands.emit(command) + } + + private suspend fun logOpenAccountIfAvailable() { + val config = configStorage.getOrNull() + if (config != null) { + analytics.sendOpenAccountEvent(config.analytics) + } else { + Timber.w("Missing config during onboarding") + } + } + + private fun sendScreenViewAnalytics() { + viewModelScope.sendAnalyticsOnboardingScreenEvent( + analytics, + EventsDictionary.ScreenOnboardingStep.PHRASE + ) + } + fun shouldShowEmail(): Boolean { val networkStatus = networkConnectionStatus.getCurrentNetworkType() if (networkStatus == DeviceNetworkType.NOT_CONNECTED) { @@ -152,13 +134,21 @@ class OnboardingMnemonicViewModel @Inject constructor( ) } + private fun sendClickAnalytics(type: ClickOnboardingButton) { + viewModelScope.sendAnalyticsOnboardingClickEvent( + analytics = analytics, + type = type, + step = EventsDictionary.ScreenOnboardingStep.PHRASE + ) + } + sealed interface State { val mnemonicPhrase: String - class Idle(override val mnemonicPhrase: String): State - class Mnemonic(override val mnemonicPhrase: String): State - class MnemonicOpened(override val mnemonicPhrase: String): State + class Idle(override val mnemonicPhrase: String) : State + class Mnemonic(override val mnemonicPhrase: String) : State + class MnemonicOpened(override val mnemonicPhrase: String) : State } class Factory @Inject constructor( @@ -166,7 +156,8 @@ class OnboardingMnemonicViewModel @Inject constructor( private val analytics: Analytics, private val configStorage: ConfigStorage, private val networkModeProvider: NetworkModeProvider, - private val networkConnectionStatus: NetworkConnectionStatus + private val networkConnectionStatus: NetworkConnectionStatus, + private val pendingIntentStore: PendingIntentStore ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { @@ -175,7 +166,8 @@ class OnboardingMnemonicViewModel @Inject constructor( analytics = analytics, configStorage = configStorage, networkModeProvider = networkModeProvider, - networkConnectionStatus = networkConnectionStatus + networkConnectionStatus = networkConnectionStatus, + pendingIntentStore = pendingIntentStore ) as T } } @@ -186,6 +178,7 @@ class OnboardingMnemonicViewModel @Inject constructor( val space: SpaceId, val startingObject: Id ) : Command() + data class NavigateToAddEmailScreen( val startingObject: String?, val space: String diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/onboarding/signup/OnboardingSetProfileNameViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/onboarding/signup/OnboardingSetProfileNameViewModel.kt index db058b959e..2c139408d1 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/onboarding/signup/OnboardingSetProfileNameViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/onboarding/signup/OnboardingSetProfileNameViewModel.kt @@ -15,6 +15,7 @@ import com.anytypeio.anytype.domain.auth.interactor.CreateAccount import com.anytypeio.anytype.domain.auth.interactor.SetupWallet import com.anytypeio.anytype.domain.base.fold import com.anytypeio.anytype.domain.config.ConfigStorage +import com.anytypeio.anytype.domain.deeplink.PendingIntentStore import com.anytypeio.anytype.domain.device.PathProvider import com.anytypeio.anytype.domain.misc.LocaleProvider import com.anytypeio.anytype.domain.`object`.ImportGetStartedUseCase @@ -54,6 +55,7 @@ class OnboardingSetProfileNameViewModel @Inject constructor( private val spaceManager: SpaceManager, private val stringProvider: StringResourceProvider, private val setMembershipEmail: SetMembershipEmail, + private val pendingIntentStore: PendingIntentStore ) : BaseViewModel() { init { @@ -295,16 +297,26 @@ class OnboardingSetProfileNameViewModel @Inject constructor( private fun proceedWithNavigation(space: Id, startingObject: String?) { viewModelScope.launch { sendOpenAccountAnalytics() - if (!startingObject.isNullOrEmpty()) { - navigation.emit( - OpenStartingObject( - space = SpaceId(space), - startingObject = startingObject - ) + navigateNextStep( + space = space, + startingObject = startingObject + ) + } + } + + private suspend fun navigateNextStep(space: Id, startingObject: Id?) { + delay(LOADING_AFTER_SUCCESS_DELAY) + val deeplink = pendingIntentStore.getDeepLinkInvite() + when { + !deeplink.isNullOrEmpty() -> navigation.emit(Navigation.OpenVault) + !startingObject.isNullOrEmpty() -> navigation.emit( + OpenStartingObject( + space = SpaceId(space), + startingObject = startingObject ) - } else { - navigation.emit(Navigation.OpenVault) - } + ) + + else -> navigation.emit(Navigation.OpenVault) } } @@ -359,7 +371,8 @@ class OnboardingSetProfileNameViewModel @Inject constructor( private val globalSubscriptionManager: GlobalSubscriptionManager, private val spaceManager: SpaceManager, private val stringProvider: StringResourceProvider, - private val setMembershipEmail: SetMembershipEmail + private val setMembershipEmail: SetMembershipEmail, + private val pendingIntentStore: PendingIntentStore ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { @@ -378,7 +391,8 @@ class OnboardingSetProfileNameViewModel @Inject constructor( globalSubscriptionManager = globalSubscriptionManager, spaceManager = spaceManager, stringProvider = stringProvider, - setMembershipEmail = setMembershipEmail + setMembershipEmail = setMembershipEmail, + pendingIntentStore = pendingIntentStore ) as T } } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/vault/VaultViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/vault/VaultViewModel.kt index 5bcfa9997f..26fddd5455 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/vault/VaultViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/vault/VaultViewModel.kt @@ -16,6 +16,7 @@ import com.anytypeio.anytype.core_models.primitives.Space import com.anytypeio.anytype.core_models.primitives.SpaceId import com.anytypeio.anytype.domain.base.fold import com.anytypeio.anytype.domain.chats.ChatPreviewContainer +import com.anytypeio.anytype.domain.deeplink.PendingIntentStore import com.anytypeio.anytype.domain.misc.AppActionManager import com.anytypeio.anytype.domain.misc.DeepLinkResolver import com.anytypeio.anytype.domain.misc.UrlBuilder @@ -48,7 +49,6 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce -import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map @@ -64,8 +64,6 @@ class VaultViewModel( private val getSpaceWallpapers: GetSpaceWallpapers, private val spaceManager: SpaceManager, private val saveCurrentSpace: SaveCurrentSpace, - private val getVaultSettings: GetVaultSettings, - private val setVaultSettings: SetVaultSettings, private val observeVaultSettings: ObserveVaultSettings, private val setVaultSpaceOrder: SetVaultSpaceOrder, private val analytics: Analytics, @@ -73,7 +71,8 @@ class VaultViewModel( private val appActionManager: AppActionManager, private val spaceInviteResolver: SpaceInviteResolver, private val profileContainer: ProfileSubscriptionManager, - private val chatPreviewContainer: ChatPreviewContainer + private val chatPreviewContainer: ChatPreviewContainer, + private val pendingIntentStore: PendingIntentStore ) : NavigationViewModel(), DeepLinkToObjectDelegate by deepLinkToObjectDelegate { val spaces = MutableStateFlow>(emptyList()) @@ -261,6 +260,17 @@ class VaultViewModel( } } + fun processPendingDeeplink() { + viewModelScope.launch { + delay(1000) // Simulate some delay + pendingIntentStore.getDeepLinkInvite()?.let { deeplink -> + Timber.d("Processing pending deeplink: $deeplink") + commands.emit(Command.Deeplink.Invite(deeplink)) + pendingIntentStore.clearDeepLinkInvite() + } + } + } + private suspend fun proceedWithSavingCurrentSpace( targetSpace: String, chat: Id?, @@ -357,8 +367,6 @@ class VaultViewModel( private val urlBuilder: UrlBuilder, private val spaceManager: SpaceManager, private val saveCurrentSpace: SaveCurrentSpace, - private val getVaultSettings: GetVaultSettings, - private val setVaultSettings: SetVaultSettings, private val setVaultSpaceOrder: SetVaultSpaceOrder, private val observeVaultSettings: ObserveVaultSettings, private val analytics: Analytics, @@ -366,7 +374,8 @@ class VaultViewModel( private val appActionManager: AppActionManager, private val spaceInviteResolver: SpaceInviteResolver, private val profileContainer: ProfileSubscriptionManager, - private val chatPreviewContainer: ChatPreviewContainer + private val chatPreviewContainer: ChatPreviewContainer, + private val pendingIntentStore: PendingIntentStore ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create( @@ -377,8 +386,6 @@ class VaultViewModel( urlBuilder = urlBuilder, spaceManager = spaceManager, saveCurrentSpace = saveCurrentSpace, - getVaultSettings = getVaultSettings, - setVaultSettings = setVaultSettings, setVaultSpaceOrder = setVaultSpaceOrder, observeVaultSettings = observeVaultSettings, analytics = analytics, @@ -386,7 +393,8 @@ class VaultViewModel( appActionManager = appActionManager, spaceInviteResolver = spaceInviteResolver, profileContainer = profileContainer, - chatPreviewContainer = chatPreviewContainer + chatPreviewContainer = chatPreviewContainer, + pendingIntentStore = pendingIntentStore ) as T }