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

DROID-3397 Deep Links | Fix | Misc. fixes (#2139)

This commit is contained in:
Evgenii Kozlov 2025-03-10 11:22:39 +01:00 committed by GitHub
parent 4f56a5ffc5
commit 9ab300175e
Signed by: github
GPG key ID: B5690EEEBB952194
9 changed files with 250 additions and 86 deletions

View file

@ -75,7 +75,9 @@ object MainEntryModule {
awaitAccountStartManager: AwaitAccountStartManager,
membershipProvider: MembershipProvider,
globalSubscriptionManager: GlobalSubscriptionManager,
spaceInviteResolver: SpaceInviteResolver
spaceInviteResolver: SpaceInviteResolver,
spaceManager: SpaceManager,
spaceViewSubscriptionContainer: SpaceViewSubscriptionContainer
): MainViewModelFactory = MainViewModelFactory(
resumeAccount = resumeAccount,
analytics = analytics,
@ -93,7 +95,9 @@ object MainEntryModule {
awaitAccountStartManager = awaitAccountStartManager,
membershipProvider = membershipProvider,
globalSubscriptionManager = globalSubscriptionManager,
spaceInviteResolver = spaceInviteResolver
spaceInviteResolver = spaceInviteResolver,
spaceManager = spaceManager,
spaceViews = spaceViewSubscriptionContainer
)
@JvmStatic

View file

@ -6,6 +6,7 @@ import com.anytypeio.anytype.core_models.Url
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.domain.misc.DeepLinkResolver
import com.anytypeio.anytype.domain.multiplayer.SpaceInviteResolver
import timber.log.Timber
const val DEEP_LINK_PATTERN = "anytype://"
@ -93,6 +94,8 @@ object DefaultDeepLinkResolver : DeepLinkResolver {
)
}
else -> DeepLinkResolver.Action.Unknown
}.also {
Timber.d("Resolving deep link: $deeplink")
}
override fun createObjectDeepLink(obj: Id, space: SpaceId): Url {

View file

@ -50,6 +50,7 @@ import com.anytypeio.anytype.presentation.notifications.NotificationAction
import com.anytypeio.anytype.presentation.notifications.NotificationCommand
import com.anytypeio.anytype.presentation.wallpaper.WallpaperColor
import com.anytypeio.anytype.presentation.wallpaper.WallpaperView
import com.anytypeio.anytype.ui.chats.ChatFragment
import com.anytypeio.anytype.ui.date.DateObjectFragment
import com.anytypeio.anytype.ui.editor.CreateObjectFragment
import com.anytypeio.anytype.ui.editor.EditorFragment
@ -193,82 +194,38 @@ class MainActivity : AppCompatActivity(R.layout.activity_main), AppNavigation.Pr
}
}
is Command.Navigate -> {
when(val dest = command.destination) {
is OpenObjectNavigation.OpenDataView -> {
runCatching {
findNavController(R.id.fragment).navigate(
R.id.dataViewNavigation,
args = ObjectSetFragment.args(
ctx = dest.target,
space = dest.space
),
navOptions = NavOptions.Builder()
.setPopUpTo(R.id.homeScreen, true)
.build()
)
}.onFailure {
Timber.e(it, "Error while data view navigation")
}
}
is OpenObjectNavigation.OpenParticipant -> {
runCatching {
findNavController(R.id.fragment).navigate(
R.id.participantScreen,
ParticipantFragment.args(
objectId = dest.target,
space = dest.space
)
)
}.onFailure {
Timber.w("Error while opening participant screen")
}
}
is OpenObjectNavigation.OpenEditor -> {
runCatching {
findNavController(R.id.fragment).navigate(
R.id.objectNavigation,
args = EditorFragment.args(
ctx = dest.target,
space = dest.space
),
navOptions = NavOptions.Builder()
.setPopUpTo(R.id.homeScreen, true)
.build()
)
}.onFailure {
Timber.e(it, "Error while editor navigation")
}
}
is OpenObjectNavigation.OpenChat -> {
toast("Cannot open chat from here")
}
is OpenObjectNavigation.UnexpectedLayoutError -> {
toast(getString(R.string.error_unexpected_layout))
}
OpenObjectNavigation.NonValidObject -> {
toast(getString(R.string.error_non_valid_object))
}
is OpenObjectNavigation.OpenDateObject -> {
runCatching {
findNavController(R.id.fragment).navigate(
R.id.dateObjectScreen,
args = DateObjectFragment.args(
objectId = dest.target,
space = dest.space
),
navOptions = Builder()
.setPopUpTo(R.id.homeScreen, true)
.build()
)
}.onFailure {
Timber.e(it, "Error while date object navigation")
}
}
}
proceedWithOpenObjectNavigation(command.destination)
}
is Command.Deeplink.DeepLinkToObjectNotWorking -> {
toast(getString(R.string.multiplayer_deeplink_to_your_object_error))
}
is Command.Deeplink.DeepLinkToObject -> {
when(val effect = command.sideEffect) {
is Command.Deeplink.DeepLinkToObject.SideEffect.SwitchSpace -> {
runCatching {
val controller = findNavController(R.id.fragment)
controller.popBackStack(R.id.vaultScreen, false)
if (effect.chat != null) {
controller.navigate(
R.id.actionOpenChatFromVault,
ChatFragment.args(
space = command.space,
ctx = effect.chat.orEmpty()
)
)
} else {
controller.navigate(R.id.actionOpenSpaceFromVault)
}
proceedWithOpenObjectNavigation(command.navigation)
}.onFailure {
Timber.e(it, "Error while switching space when handling deep link to object")
}
}
null -> {
proceedWithOpenObjectNavigation(command.navigation)
}
}
}
is Command.Deeplink.GalleryInstallation -> {
runCatching {
findNavController(R.id.fragment).navigate(
@ -319,6 +276,84 @@ class MainActivity : AppCompatActivity(R.layout.activity_main), AppNavigation.Pr
}
}
private fun proceedWithOpenObjectNavigation(dest: OpenObjectNavigation) {
when (dest) {
is OpenObjectNavigation.OpenDataView -> {
runCatching {
findNavController(R.id.fragment).navigate(
R.id.dataViewNavigation,
args = ObjectSetFragment.args(
ctx = dest.target,
space = dest.space
),
navOptions = Builder()
.setPopUpTo(R.id.homeScreen, true)
.build()
)
}.onFailure {
Timber.e(it, "Error while data view navigation")
}
}
is OpenObjectNavigation.OpenParticipant -> {
runCatching {
findNavController(R.id.fragment).navigate(
R.id.participantScreen,
ParticipantFragment.args(
objectId = dest.target,
space = dest.space
)
)
}.onFailure {
Timber.w("Error while opening participant screen")
}
}
is OpenObjectNavigation.OpenEditor -> {
runCatching {
findNavController(R.id.fragment).navigate(
R.id.objectNavigation,
args = EditorFragment.args(
ctx = dest.target,
space = dest.space
)
)
}.onFailure {
Timber.e(it, "Error while editor navigation")
}
}
is OpenObjectNavigation.OpenChat -> {
toast("Cannot open chat from here")
}
is OpenObjectNavigation.UnexpectedLayoutError -> {
toast(getString(R.string.error_unexpected_layout))
}
OpenObjectNavigation.NonValidObject -> {
toast(getString(R.string.error_non_valid_object))
}
is OpenObjectNavigation.OpenDateObject -> {
runCatching {
findNavController(R.id.fragment).navigate(
R.id.dateObjectScreen,
args = DateObjectFragment.args(
objectId = dest.target,
space = dest.space
),
navOptions = Builder()
.setPopUpTo(R.id.homeScreen, true)
.build()
)
}.onFailure {
Timber.e(it, "Error while date object navigation")
}
}
}
}
private fun startAppUpdater() {
if (featureToggles.isAutoUpdateEnabled) {
AppUpdater(this)

View file

@ -63,9 +63,6 @@ class CreateSpaceFragment : BaseBottomSheetComposeFragment() {
deeplink = null
)
)
// if (command.showMultiplayerTooltip) {
// findNavController().navigate(R.id.multiplayerFeatureDialog)
// }
}.onFailure {
Timber.e(it, "Error while exiting to vault or opening created space")
}

View file

@ -1,7 +1,9 @@
package com.anytypeio.anytype.other
import android.os.Build
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.domain.misc.DeepLinkResolver
import com.anytypeio.anytype.test_utils.MockDataFactory
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
@ -26,6 +28,29 @@ class DefaultDeepLinkResolverTest {
assertEquals(DeepLinkResolver.Action.Import.Experience(type = "experience123", source = "source321"), result)
}
@Test
fun `resolve link to object deep link`() {
// Given
val obj = MockDataFactory.randomUuid()
val space = MockDataFactory.randomUuid()
val deeplink = "anytype://object?objectId=$obj&spaceId=$space"
// When
val result = deepLinkResolver.resolve(deeplink)
// Then
assertEquals(
DeepLinkResolver.Action.DeepLinkToObject(
space = SpaceId(space),
obj = obj
),
result
)
}
@Test
fun `resolve returns Invite with deeplink for invite deep links`() {
// Given

View file

@ -25,10 +25,12 @@ import com.anytypeio.anytype.domain.config.ConfigStorage
import com.anytypeio.anytype.domain.misc.DeepLinkResolver
import com.anytypeio.anytype.domain.misc.LocaleProvider
import com.anytypeio.anytype.domain.multiplayer.SpaceInviteResolver
import com.anytypeio.anytype.domain.multiplayer.SpaceViewSubscriptionContainer
import com.anytypeio.anytype.domain.notifications.SystemNotificationService
import com.anytypeio.anytype.domain.subscriptions.GlobalSubscriptionManager
import com.anytypeio.anytype.domain.wallpaper.ObserveWallpaper
import com.anytypeio.anytype.domain.wallpaper.RestoreWallpaper
import com.anytypeio.anytype.domain.workspace.SpaceManager
import com.anytypeio.anytype.presentation.home.OpenObjectNavigation
import com.anytypeio.anytype.presentation.home.navigation
import com.anytypeio.anytype.presentation.membership.provider.MembershipProvider
@ -64,7 +66,9 @@ class MainViewModel(
private val awaitAccountStartManager: AwaitAccountStartManager,
private val membershipProvider: MembershipProvider,
private val globalSubscriptionManager: GlobalSubscriptionManager,
private val spaceInviteResolver: SpaceInviteResolver
private val spaceInviteResolver: SpaceInviteResolver,
private val spaceManager: SpaceManager,
private val spaceViews: SpaceViewSubscriptionContainer
) : ViewModel(),
NotificationActionDelegate by notificationActionDelegate,
DeepLinkToObjectDelegate by deepLinkToObjectDelegate {
@ -334,9 +338,8 @@ class MainViewModel(
val result = onDeepLinkToObject(
obj = deeplink.obj,
space = deeplink.space,
switchSpaceIfObjectFound = true
switchSpaceIfObjectFound = false
)
Timber.d("Deep link to object result: $result")
when (result) {
is DeepLinkToObjectDelegate.Result.Error -> {
val link = deeplink.invite
@ -354,9 +357,59 @@ class MainViewModel(
}
}
is DeepLinkToObjectDelegate.Result.Success -> {
commands.emit(
Command.Navigate(result.obj.navigation())
)
val targetSpace = result.obj.spaceId
if (targetSpace != null) {
val currentState = spaceManager.getState()
Timber.d("Space manager state before processing deep link: $currentState")
when(currentState) {
SpaceManager.State.Init -> {
// Do nothing.
}
SpaceManager.State.NoSpace -> {
proceedWithSpaceSwitchDueToDeepLinkToObject(
targetSpace = targetSpace,
deeplink = deeplink,
result = result
)
}
is SpaceManager.State.Space.Idle -> {
if (currentState.space.id != deeplink.space.id) {
proceedWithSpaceSwitchDueToDeepLinkToObject(
targetSpace = targetSpace,
deeplink = deeplink,
result = result
)
} else {
commands.emit(
Command.Deeplink.DeepLinkToObject(
navigation = result.obj.navigation(),
sideEffect = null,
space = deeplink.space.id,
obj = deeplink.obj
),
)
}
}
is SpaceManager.State.Space.Active -> {
if (currentState.config.space != deeplink.space.id) {
proceedWithSpaceSwitchDueToDeepLinkToObject(
targetSpace = targetSpace,
deeplink = deeplink,
result = result
)
} else {
commands.emit(
Command.Deeplink.DeepLinkToObject(
navigation = result.obj.navigation(),
sideEffect = null,
space = deeplink.space.id,
obj = deeplink.obj
),
)
}
}
}
}
}
}
}
@ -371,6 +424,33 @@ class MainViewModel(
}
}
private suspend fun proceedWithSpaceSwitchDueToDeepLinkToObject(
targetSpace: Id,
deeplink: DeepLinkResolver.Action.DeepLinkToObject,
result: DeepLinkToObjectDelegate.Result.Success
) {
spaceManager
.set(targetSpace)
.onSuccess { config ->
val home = config.home
val spaceView = spaceViews
.get(deeplink.space)
val chat = spaceView?.chatId?.ifEmpty { null }
val sideEffect = Command.Deeplink.DeepLinkToObject.SideEffect.SwitchSpace(
chat = chat,
home = home
)
commands.emit(
Command.Deeplink.DeepLinkToObject(
navigation = result.obj.navigation(),
sideEffect = sideEffect,
space = deeplink.space.id,
obj = deeplink.obj
),
)
}
}
sealed class Command {
data class ShowDeletedAccountScreen(val deadline: Long) : Command()
data object LogoutDueToAccountDeletion : Command()
@ -391,6 +471,19 @@ class MainViewModel(
sealed class Deeplink : Command() {
data object DeepLinkToObjectNotWorking: Deeplink()
data class DeepLinkToObject(
val obj: Id,
val space: Id,
val navigation: OpenObjectNavigation,
val sideEffect: SideEffect? = null
) : Deeplink() {
sealed class SideEffect {
data class SwitchSpace(
val home: Id,
val chat: Id?
): SideEffect()
}
}
data class Invite(val link: String) : Deeplink()
data class GalleryInstallation(
val deepLinkType: String,

View file

@ -11,10 +11,12 @@ import com.anytypeio.anytype.domain.auth.interactor.ResumeAccount
import com.anytypeio.anytype.domain.config.ConfigStorage
import com.anytypeio.anytype.domain.misc.LocaleProvider
import com.anytypeio.anytype.domain.multiplayer.SpaceInviteResolver
import com.anytypeio.anytype.domain.multiplayer.SpaceViewSubscriptionContainer
import com.anytypeio.anytype.domain.notifications.SystemNotificationService
import com.anytypeio.anytype.domain.subscriptions.GlobalSubscriptionManager
import com.anytypeio.anytype.domain.wallpaper.ObserveWallpaper
import com.anytypeio.anytype.domain.wallpaper.RestoreWallpaper
import com.anytypeio.anytype.domain.workspace.SpaceManager
import com.anytypeio.anytype.presentation.membership.provider.MembershipProvider
import com.anytypeio.anytype.presentation.navigation.DeepLinkToObjectDelegate
import com.anytypeio.anytype.presentation.notifications.NotificationActionDelegate
@ -38,7 +40,9 @@ class MainViewModelFactory @Inject constructor(
private val awaitAccountStartManager: AwaitAccountStartManager,
private val membershipProvider: MembershipProvider,
private val globalSubscriptionManager: GlobalSubscriptionManager,
private val spaceInviteResolver: SpaceInviteResolver
private val spaceInviteResolver: SpaceInviteResolver,
private val spaceManager: SpaceManager,
private val spaceViews: SpaceViewSubscriptionContainer
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(
@ -60,6 +64,8 @@ class MainViewModelFactory @Inject constructor(
awaitAccountStartManager = awaitAccountStartManager,
membershipProvider = membershipProvider,
globalSubscriptionManager = globalSubscriptionManager,
spaceInviteResolver = spaceInviteResolver
spaceInviteResolver = spaceInviteResolver,
spaceManager = spaceManager,
spaceViews = spaceViews
) as T
}

View file

@ -346,6 +346,7 @@ class SplashViewModel(
* @see [LaunchAccount] use-case
*/
private suspend fun proceedWithVaultNavigation(deeplink: String? = null) {
Timber.d("proceedWithVaultNavigation deep link: $deeplink")
val space = getLastOpenedSpace.async(Unit).getOrNull()
if (space != null && spaceManager.getState() != SpaceManager.State.NoSpace) {
spaceManager

View file

@ -175,7 +175,7 @@ class VaultViewModel(
}
fun onResume(deeplink: DeepLinkResolver.Action? = null) {
Timber.d("onResume")
Timber.d("onResume, deep link: $deeplink")
viewModelScope.launch {
analytics.sendEvent(
eventName = EventsDictionary.screenVault,