mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-08 05:47:05 +09:00
DROID-1776 Gallery experience | Enhancement | Resolving deep link (#1011)
This commit is contained in:
parent
7454fc8961
commit
9104566899
28 changed files with 550 additions and 40 deletions
|
@ -107,6 +107,7 @@ import com.anytypeio.anytype.di.feature.wallpaper.WallpaperSelectModule
|
|||
import com.anytypeio.anytype.di.feature.widgets.SelectWidgetSourceModule
|
||||
import com.anytypeio.anytype.di.feature.widgets.SelectWidgetTypeModule
|
||||
import com.anytypeio.anytype.di.main.MainComponent
|
||||
import com.anytypeio.anytype.gallery_experience.viewmodel.GalleryInstallationViewModel
|
||||
import com.anytypeio.anytype.presentation.multiplayer.RequestJoinSpaceViewModel
|
||||
import com.anytypeio.anytype.presentation.multiplayer.ShareSpaceViewModel
|
||||
import com.anytypeio.anytype.presentation.multiplayer.SpaceJoinRequestViewModel
|
||||
|
@ -1076,9 +1077,13 @@ class ComponentManager(
|
|||
DaggerPaymentsComponent.factory().create(findComponentDependencies())
|
||||
}
|
||||
|
||||
val galleryInstallationsComponent = Component {
|
||||
DaggerGalleryInstallationComponent.factory().create(findComponentDependencies())
|
||||
}
|
||||
val galleryInstallationsComponent =
|
||||
ComponentWithParams { params: GalleryInstallationViewModel.ViewModelParams ->
|
||||
DaggerGalleryInstallationComponent.builder()
|
||||
.withDependencies(findComponentDependencies())
|
||||
.withParams(params)
|
||||
.build()
|
||||
}
|
||||
|
||||
class Component<T>(private val builder: () -> T) {
|
||||
|
||||
|
|
|
@ -4,11 +4,18 @@ import androidx.lifecycle.ViewModelProvider
|
|||
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.base.AppCoroutineDispatchers
|
||||
import com.anytypeio.anytype.domain.block.repo.BlockRepository
|
||||
import com.anytypeio.anytype.domain.misc.UrlBuilder
|
||||
import com.anytypeio.anytype.gallery_experience.viewmodel.GalleryInstallationViewModel
|
||||
import com.anytypeio.anytype.gallery_experience.viewmodel.GalleryInstallationViewModelFactory
|
||||
import com.anytypeio.anytype.presentation.spaces.SpaceGradientProvider
|
||||
import com.anytypeio.anytype.ui.gallery.GalleryInstallationFragment
|
||||
import dagger.Binds
|
||||
import dagger.BindsInstance
|
||||
import dagger.Component
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
||||
@Component(
|
||||
dependencies = [GalleryInstallationComponentDependencies::class],
|
||||
|
@ -20,9 +27,13 @@ import dagger.Module
|
|||
@PerScreen
|
||||
interface GalleryInstallationComponent {
|
||||
|
||||
@Component.Factory
|
||||
interface Factory {
|
||||
fun create(dependencies: GalleryInstallationComponentDependencies): GalleryInstallationComponent
|
||||
@Component.Builder
|
||||
interface Builder {
|
||||
fun withDependencies(dependencies: GalleryInstallationComponentDependencies): Builder
|
||||
|
||||
@BindsInstance
|
||||
fun withParams(params: GalleryInstallationViewModel.ViewModelParams): Builder
|
||||
fun build(): GalleryInstallationComponent
|
||||
}
|
||||
|
||||
fun inject(fragment: GalleryInstallationFragment)
|
||||
|
@ -31,6 +42,12 @@ interface GalleryInstallationComponent {
|
|||
@Module
|
||||
object GalleryInstallationModule {
|
||||
|
||||
@Provides
|
||||
@PerScreen
|
||||
fun provideGradientProvider(): SpaceGradientProvider {
|
||||
return SpaceGradientProvider.Default
|
||||
}
|
||||
|
||||
@Module
|
||||
interface Declarations {
|
||||
|
||||
|
@ -44,5 +61,8 @@ object GalleryInstallationModule {
|
|||
}
|
||||
|
||||
interface GalleryInstallationComponentDependencies : ComponentDependencies {
|
||||
fun blockRepository(): BlockRepository
|
||||
fun appCoroutineDispatchers(): AppCoroutineDispatchers
|
||||
fun analytics(): Analytics
|
||||
fun urlBuilder(): UrlBuilder
|
||||
}
|
|
@ -4,6 +4,7 @@ import android.net.Uri
|
|||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.domain.misc.DeepLinkResolver
|
||||
import com.anytypeio.anytype.domain.multiplayer.SpaceInviteResolver
|
||||
import timber.log.Timber
|
||||
|
||||
const val DEEP_LINK_PATTERN = "anytype://"
|
||||
|
||||
|
@ -12,22 +13,36 @@ const val IMPORT_PATH = "import"
|
|||
const val INVITE_PATH = "invite"
|
||||
|
||||
const val TYPE_PARAM = "type"
|
||||
const val SOURCE_PARAM = "source"
|
||||
const val TYPE_VALUE_EXPERIENCE = "experience"
|
||||
|
||||
const val IMPORT_EXPERIENCE_DEEPLINK = "$DEEP_LINK_PATTERN$MAIN_PATH/$IMPORT_PATH?$TYPE_PARAM=$TYPE_VALUE_EXPERIENCE"
|
||||
const val IMPORT_EXPERIENCE_DEEPLINK =
|
||||
"$DEEP_LINK_PATTERN$MAIN_PATH/$IMPORT_PATH/?$TYPE_PARAM=$TYPE_VALUE_EXPERIENCE"
|
||||
|
||||
object DefaultDeepLinkResolver : DeepLinkResolver {
|
||||
override fun resolve(
|
||||
deeplink: String
|
||||
): DeepLinkResolver.Action = when {
|
||||
deeplink.contains(IMPORT_EXPERIENCE_DEEPLINK) -> {
|
||||
DeepLinkResolver.Action.Import.Experience
|
||||
try {
|
||||
val type = Uri.parse(deeplink).getQueryParameter(TYPE_PARAM)
|
||||
val source = Uri.parse(deeplink).getQueryParameter(SOURCE_PARAM)
|
||||
DeepLinkResolver.Action.Import.Experience(
|
||||
type = type.orEmpty(),
|
||||
source = source.orEmpty()
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Error while resolving deeplink: $deeplink")
|
||||
DeepLinkResolver.Action.Unknown
|
||||
}
|
||||
}
|
||||
|
||||
deeplink.contains(INVITE_PATH) -> {
|
||||
DeepLinkResolver.Action.Invite(deeplink)
|
||||
}
|
||||
|
||||
else -> DeepLinkResolver.Action.Unknown
|
||||
}
|
||||
}.also { Timber.d("Resolved deeplink: $deeplink to action: $it") }
|
||||
}
|
||||
|
||||
object DefaultSpaceInviteResolver : SpaceInviteResolver {
|
||||
|
|
|
@ -7,6 +7,7 @@ import android.view.ViewGroup
|
|||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavHostController
|
||||
|
@ -14,6 +15,7 @@ import androidx.navigation.compose.NavHost
|
|||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.anytypeio.anytype.core_ui.common.ComposeDialogView
|
||||
import com.anytypeio.anytype.core_utils.ext.argString
|
||||
import com.anytypeio.anytype.core_utils.ext.subscribe
|
||||
import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetComposeFragment
|
||||
import com.anytypeio.anytype.di.common.componentManager
|
||||
|
@ -31,6 +33,9 @@ import javax.inject.Inject
|
|||
|
||||
class GalleryInstallationFragment : BaseBottomSheetComposeFragment() {
|
||||
|
||||
private val deepLinkType get() = argString(DEEPLINK_TYPE_KEY)
|
||||
private val deepLinkSource get() = argString(DEEPLINK_SOURCE_KEY)
|
||||
|
||||
@Inject
|
||||
lateinit var factory: GalleryInstallationViewModelFactory
|
||||
private val vm by viewModels<GalleryInstallationViewModel> { factory }
|
||||
|
@ -61,7 +66,9 @@ class GalleryInstallationFragment : BaseBottomSheetComposeFragment() {
|
|||
GalleryInstallationNavigation.Main -> navController.navigate(
|
||||
GalleryInstallationNavigation.Main.route
|
||||
)
|
||||
|
||||
GalleryInstallationNavigation.Spaces -> navController.navigate(
|
||||
GalleryInstallationNavigation.Spaces.route
|
||||
)
|
||||
GalleryInstallationNavigation.Dismiss -> navController.popBackStack()
|
||||
else -> {}
|
||||
}
|
||||
|
@ -100,20 +107,42 @@ class GalleryInstallationFragment : BaseBottomSheetComposeFragment() {
|
|||
skipCollapsed()
|
||||
expand()
|
||||
GalleryInstallationScreen(
|
||||
state = vm.mainState.collectAsStateWithLifecycle().value
|
||||
state = vm.mainState.collectAsStateWithLifecycle().value,
|
||||
onInstallClicked = vm::onInstallClicked
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InitSpacesScreen() {
|
||||
GalleryInstallationSpacesScreen()
|
||||
GalleryInstallationSpacesScreen(
|
||||
state = vm.spacesViewState.collectAsStateWithLifecycle().value,
|
||||
onNewSpaceClick = vm::onNewSpaceClick,
|
||||
onSpaceClick = vm::onSpaceClick,
|
||||
onDismiss = vm::onDismiss
|
||||
)
|
||||
}
|
||||
|
||||
override fun injectDependencies() {
|
||||
componentManager().galleryInstallationsComponent.get().inject(this)
|
||||
val params = GalleryInstallationViewModel.ViewModelParams(
|
||||
deepLinkType = deepLinkType,
|
||||
deepLinkSource = deepLinkSource
|
||||
)
|
||||
componentManager().galleryInstallationsComponent.get(params).inject(this)
|
||||
}
|
||||
|
||||
override fun releaseDependencies() {
|
||||
componentManager().galleryInstallationsComponent.release()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val DEEPLINK_TYPE_KEY = "arg.gallery-installation.deeplink-type-key"
|
||||
const val DEEPLINK_SOURCE_KEY = "arg.gallery-installation.deeplink-source-key"
|
||||
fun args(
|
||||
deepLinkType: String,
|
||||
deepLinkSource: String
|
||||
): Bundle = bundleOf(
|
||||
DEEPLINK_TYPE_KEY to deepLinkType,
|
||||
DEEPLINK_SOURCE_KEY to deepLinkSource
|
||||
)
|
||||
}
|
||||
}
|
|
@ -30,6 +30,7 @@ import com.anytypeio.anytype.presentation.home.HomeScreenViewModel
|
|||
import com.anytypeio.anytype.presentation.home.HomeScreenViewModel.Navigation
|
||||
import com.anytypeio.anytype.presentation.widgets.DropDownMenuAction
|
||||
import com.anytypeio.anytype.ui.base.navigation
|
||||
import com.anytypeio.anytype.ui.gallery.GalleryInstallationFragment
|
||||
import com.anytypeio.anytype.ui.main.MainActivity
|
||||
import com.anytypeio.anytype.ui.multiplayer.RequestJoinSpaceFragment
|
||||
import com.anytypeio.anytype.ui.multiplayer.ShareSpaceFragment
|
||||
|
@ -252,6 +253,15 @@ class HomeScreenFragment : BaseComposeFragment() {
|
|||
RequestJoinSpaceFragment.args(link = command.link)
|
||||
)
|
||||
}
|
||||
is Command.Deeplink.GalleryInstallation -> {
|
||||
findNavController().navigate(
|
||||
R.id.galleryInstallationScreen,
|
||||
GalleryInstallationFragment.args(
|
||||
deepLinkType = command.deepLinkType,
|
||||
deepLinkSource = command.deepLinkSource
|
||||
)
|
||||
)
|
||||
}
|
||||
is Command.ShareSpace -> {
|
||||
findNavController().navigate(
|
||||
R.id.shareSpaceScreen,
|
||||
|
|
|
@ -279,6 +279,10 @@
|
|||
android:id="@+id/paymentsScreen"
|
||||
android:name="com.anytypeio.anytype.ui.payments.PaymentsFragment" />
|
||||
|
||||
<dialog
|
||||
android:id="@+id/galleryInstallationScreen"
|
||||
android:name="com.anytypeio.anytype.ui.gallery.GalleryInstallationFragment" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/splashScreen"
|
||||
android:name="com.anytypeio.anytype.ui.splash.SplashFragment"
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
package com.anytypeio.anytype.other
|
||||
|
||||
import com.anytypeio.anytype.domain.misc.DeepLinkResolver
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class DefaultDeepLinkResolverTest {
|
||||
|
||||
private val deepLinkResolver = DefaultDeepLinkResolver
|
||||
|
||||
@Test
|
||||
fun `resolve returns Import Experience for import experience deeplinks`() {
|
||||
// Given
|
||||
val deeplink = "anytype://main/import/?type=experience"
|
||||
|
||||
// When
|
||||
val result = deepLinkResolver.resolve(deeplink)
|
||||
|
||||
// Then
|
||||
assertEquals(DeepLinkResolver.Action.Import.Experience, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resolve returns Invite with deeplink for invite deeplinks`() {
|
||||
// Given
|
||||
val deeplink = "anytype://invite/some_unique_identifier"
|
||||
|
||||
// When
|
||||
val result = deepLinkResolver.resolve(deeplink)
|
||||
|
||||
// Then
|
||||
assert(result is DeepLinkResolver.Action.Invite)
|
||||
assertEquals(deeplink, (result as DeepLinkResolver.Action.Invite).link)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resolve returns Unknown for unrecognized deeplinks`() {
|
||||
// Given
|
||||
val deeplink = "anytype://some_random_path"
|
||||
|
||||
// When
|
||||
val result = deepLinkResolver.resolve(deeplink)
|
||||
|
||||
// Then
|
||||
assertEquals(DeepLinkResolver.Action.Unknown, result)
|
||||
}
|
||||
}
|
|
@ -460,4 +460,13 @@ sealed class Command {
|
|||
val inviteContentId: Id,
|
||||
val inviteFileKey: String
|
||||
)
|
||||
|
||||
data class DownloadGalleryManifest(val url: String)
|
||||
|
||||
data class ImportExperience(
|
||||
val space: SpaceId,
|
||||
val url: String,
|
||||
val title: String,
|
||||
val isNewSpace: Boolean
|
||||
)
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package com.anytypeio.anytype.core_models
|
||||
|
||||
data class ManifestInfo(
|
||||
val schema: String,
|
||||
val id: String,
|
||||
val name: String,
|
||||
val author: String,
|
||||
val license: String,
|
||||
val title: String,
|
||||
val description: String,
|
||||
val screenshots: List<String>,
|
||||
val downloadLink: String,
|
||||
val fileSize: Int,
|
||||
val categories: List<String>,
|
||||
val language: String
|
||||
)
|
|
@ -11,6 +11,7 @@ import com.anytypeio.anytype.core_models.DVViewer
|
|||
import com.anytypeio.anytype.core_models.DVViewerType
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.Key
|
||||
import com.anytypeio.anytype.core_models.ManifestInfo
|
||||
import com.anytypeio.anytype.core_models.NodeUsageInfo
|
||||
import com.anytypeio.anytype.core_models.ObjectType
|
||||
import com.anytypeio.anytype.core_models.ObjectView
|
||||
|
@ -964,4 +965,12 @@ class BlockDataRepository(
|
|||
override suspend fun getSpaceInviteLink(spaceId: SpaceId): SpaceInviteLink {
|
||||
return remote.getSpaceInviteLink(spaceId)
|
||||
}
|
||||
|
||||
override suspend fun downloadGalleryManifest(command: Command.DownloadGalleryManifest): ManifestInfo? {
|
||||
return remote.downloadGalleryManifest(command)
|
||||
}
|
||||
|
||||
override suspend fun importExperience(command: Command.ImportExperience): Payload {
|
||||
return remote.importExperience(command)
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ import com.anytypeio.anytype.core_models.DVViewer
|
|||
import com.anytypeio.anytype.core_models.DVViewerType
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.Key
|
||||
import com.anytypeio.anytype.core_models.ManifestInfo
|
||||
import com.anytypeio.anytype.core_models.NodeUsageInfo
|
||||
import com.anytypeio.anytype.core_models.ObjectType
|
||||
import com.anytypeio.anytype.core_models.ObjectView
|
||||
|
@ -412,4 +413,7 @@ interface BlockRemote {
|
|||
suspend fun stopSharingSpace(space: SpaceId)
|
||||
|
||||
suspend fun getSpaceInviteLink(spaceId: SpaceId): SpaceInviteLink
|
||||
|
||||
suspend fun downloadGalleryManifest(command: Command.DownloadGalleryManifest): ManifestInfo?
|
||||
suspend fun importExperience(command: Command.ImportExperience): Payload
|
||||
}
|
|
@ -11,6 +11,7 @@ import com.anytypeio.anytype.core_models.DVViewer
|
|||
import com.anytypeio.anytype.core_models.DVViewerType
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.Key
|
||||
import com.anytypeio.anytype.core_models.ManifestInfo
|
||||
import com.anytypeio.anytype.core_models.NodeUsageInfo
|
||||
import com.anytypeio.anytype.core_models.ObjectType
|
||||
import com.anytypeio.anytype.core_models.ObjectView
|
||||
|
@ -455,4 +456,7 @@ interface BlockRepository {
|
|||
suspend fun stopSharingSpace(space: SpaceId)
|
||||
|
||||
suspend fun getSpaceInviteLink(spaceId: SpaceId): SpaceInviteLink
|
||||
|
||||
suspend fun downloadGalleryManifest(command: Command.DownloadGalleryManifest): ManifestInfo?
|
||||
suspend fun importExperience(command: Command.ImportExperience): Payload
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package com.anytypeio.anytype.domain.gallery_experience
|
||||
|
||||
import com.anytypeio.anytype.core_models.Command
|
||||
import com.anytypeio.anytype.core_models.ManifestInfo
|
||||
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 DownloadGalleryManifest @Inject constructor(
|
||||
dispatchers: AppCoroutineDispatchers,
|
||||
private val repo: BlockRepository
|
||||
) : ResultInteractor<DownloadGalleryManifest.Params, ManifestInfo?>(dispatchers.io) {
|
||||
|
||||
override suspend fun doWork(params: Params): ManifestInfo? {
|
||||
val command = Command.DownloadGalleryManifest(
|
||||
url = params.url
|
||||
)
|
||||
return repo.downloadGalleryManifest(command)
|
||||
}
|
||||
|
||||
class Params(val url: String)
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package com.anytypeio.anytype.domain.gallery_experience
|
||||
|
||||
import com.anytypeio.anytype.core_models.Command
|
||||
import com.anytypeio.anytype.core_models.Payload
|
||||
import com.anytypeio.anytype.core_models.primitives.SpaceId
|
||||
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 ImportExperience @Inject constructor(
|
||||
dispatchers: AppCoroutineDispatchers,
|
||||
private val repo: BlockRepository
|
||||
) : ResultInteractor<ImportExperience.Params, Payload>(dispatchers.io) {
|
||||
|
||||
override suspend fun doWork(params: Params): Payload {
|
||||
val command = Command.ImportExperience(
|
||||
space = params.spaceId,
|
||||
url = params.url,
|
||||
title = params.title,
|
||||
isNewSpace = params.isNewSpace
|
||||
)
|
||||
return repo.importExperience(command)
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val spaceId: SpaceId,
|
||||
val url: String,
|
||||
val title: String,
|
||||
val isNewSpace: Boolean
|
||||
)
|
||||
}
|
|
@ -7,7 +7,7 @@ interface DeepLinkResolver {
|
|||
sealed class Action {
|
||||
object Unknown : Action()
|
||||
sealed class Import : Action() {
|
||||
object Experience : Action()
|
||||
data class Experience(val type: String, val source: String) : Action()
|
||||
}
|
||||
data class Invite(val link: String) : Action()
|
||||
}
|
||||
|
|
|
@ -1,15 +1,29 @@
|
|||
package com.anytypeio.anytype.gallery_experience.models
|
||||
|
||||
import com.anytypeio.anytype.core_models.ManifestInfo
|
||||
import com.anytypeio.anytype.core_models.ObjectWrapper
|
||||
import com.anytypeio.anytype.presentation.spaces.SpaceIconView
|
||||
|
||||
sealed class GalleryInstallationState {
|
||||
object Hidden : GalleryInstallationState()
|
||||
object Loading : GalleryInstallationState()
|
||||
object Success : GalleryInstallationState()
|
||||
data class Success(val info: ManifestInfo) : GalleryInstallationState()
|
||||
}
|
||||
|
||||
data class GalleryInstallationSpacesState(
|
||||
val spaces: List<GallerySpaceView>,
|
||||
val isNewButtonVisible: Boolean
|
||||
)
|
||||
|
||||
sealed class GalleryInstallationNavigation(val route: String) {
|
||||
object Main : GalleryInstallationNavigation("main")
|
||||
object Spaces : GalleryInstallationNavigation("spaces")
|
||||
object Success : GalleryInstallationNavigation("success")
|
||||
object Error : GalleryInstallationNavigation("error")
|
||||
object Dismiss : GalleryInstallationNavigation("")
|
||||
}
|
||||
}
|
||||
|
||||
data class GallerySpaceView(
|
||||
val obj: ObjectWrapper.SpaceView,
|
||||
val icon: SpaceIconView
|
||||
)
|
|
@ -1,16 +1,11 @@
|
|||
package com.anytypeio.anytype.gallery_experience.screens
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.anytypeio.anytype.gallery_experience.models.GalleryInstallationState
|
||||
|
||||
@Composable
|
||||
fun GalleryInstallationScreen(state: GalleryInstallationState) {
|
||||
}
|
||||
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun GalleryInstallationScreenPreview() {
|
||||
GalleryInstallationScreen(GalleryInstallationState.Success)
|
||||
fun GalleryInstallationScreen(
|
||||
state: GalleryInstallationState,
|
||||
onInstallClicked: () -> Unit
|
||||
) {
|
||||
}
|
|
@ -1,14 +1,17 @@
|
|||
package com.anytypeio.anytype.gallery_experience.screens
|
||||
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.anytypeio.anytype.gallery_experience.models.GalleryInstallationSpacesState
|
||||
import com.anytypeio.anytype.gallery_experience.models.GallerySpaceView
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun GalleryInstallationSpacesScreen() {
|
||||
}
|
||||
fun GalleryInstallationSpacesScreen(
|
||||
state: GalleryInstallationSpacesState,
|
||||
onNewSpaceClick: () -> Unit,
|
||||
onSpaceClick: (GallerySpaceView) -> Unit,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun GallerySpacesScreenPreview() {
|
||||
GalleryInstallationSpacesScreen()
|
||||
}
|
|
@ -1,20 +1,165 @@
|
|||
package com.anytypeio.anytype.gallery_experience.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.anytypeio.anytype.analytics.base.Analytics
|
||||
import com.anytypeio.anytype.core_models.ManifestInfo
|
||||
import com.anytypeio.anytype.core_models.ObjectWrapper
|
||||
import com.anytypeio.anytype.core_models.Relations
|
||||
import com.anytypeio.anytype.core_models.primitives.SpaceId
|
||||
import com.anytypeio.anytype.domain.base.fold
|
||||
import com.anytypeio.anytype.domain.gallery_experience.DownloadGalleryManifest
|
||||
import com.anytypeio.anytype.domain.gallery_experience.ImportExperience
|
||||
import com.anytypeio.anytype.domain.misc.UrlBuilder
|
||||
import com.anytypeio.anytype.domain.spaces.CreateSpace
|
||||
import com.anytypeio.anytype.domain.spaces.GetSpaceViews
|
||||
import com.anytypeio.anytype.gallery_experience.models.GalleryInstallationNavigation
|
||||
import com.anytypeio.anytype.gallery_experience.models.GalleryInstallationSpacesState
|
||||
import com.anytypeio.anytype.gallery_experience.models.GalleryInstallationState
|
||||
import com.anytypeio.anytype.gallery_experience.models.GallerySpaceView
|
||||
import com.anytypeio.anytype.presentation.spaces.SpaceGradientProvider
|
||||
import com.anytypeio.anytype.presentation.spaces.spaceIcon
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
class GalleryInstallationViewModel(
|
||||
private val viewModelParams: ViewModelParams,
|
||||
private val downloadGalleryManifest: DownloadGalleryManifest,
|
||||
private val importExperience: ImportExperience,
|
||||
private val analytics: Analytics,
|
||||
private val getSpaceViews: GetSpaceViews,
|
||||
private val createSpace: CreateSpace,
|
||||
private val urlBuilder: UrlBuilder,
|
||||
private val spaceGradientProvider: SpaceGradientProvider
|
||||
) : ViewModel() {
|
||||
|
||||
val mainState = MutableStateFlow<GalleryInstallationState>(GalleryInstallationState.Hidden)
|
||||
val mainState = MutableStateFlow<GalleryInstallationState>(GalleryInstallationState.Loading)
|
||||
val spacesViewState =
|
||||
MutableStateFlow(GalleryInstallationSpacesState(emptyList(), false))
|
||||
val command = MutableStateFlow<GalleryInstallationNavigation?>(null)
|
||||
|
||||
init {
|
||||
Timber.d("GalleryInstallationViewModel init")
|
||||
Timber.d("GalleryInstallationViewModel init, viewModelParams: $viewModelParams")
|
||||
downloadGalleryManifest()
|
||||
}
|
||||
}
|
||||
|
||||
private fun downloadGalleryManifest() {
|
||||
viewModelScope.launch {
|
||||
val params = DownloadGalleryManifest.Params(url = viewModelParams.deepLinkSource)
|
||||
downloadGalleryManifest.async(params).fold(
|
||||
onSuccess = { manifestInfo ->
|
||||
if (manifestInfo != null) {
|
||||
Timber.d("DownloadGalleryManifest success, manifestInfo: $manifestInfo")
|
||||
mainState.value = GalleryInstallationState.Success(manifestInfo)
|
||||
} else {
|
||||
Timber.e("DownloadGalleryManifest failed, manifestInfo is null")
|
||||
command.value = GalleryInstallationNavigation.Error
|
||||
}
|
||||
},
|
||||
onFailure = { error ->
|
||||
Timber.e(error, "DownloadGalleryManifest failed")
|
||||
command.value = GalleryInstallationNavigation.Dismiss
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun onInstallClicked() {
|
||||
viewModelScope.launch {
|
||||
getSpaceViews.async(Unit).fold(
|
||||
onSuccess = { spaces ->
|
||||
Timber.d("GetSpaceViews success, spaceViews: $spaces")
|
||||
spacesViewState.value = GalleryInstallationSpacesState(
|
||||
spaces = spaces.map {
|
||||
it.toView(urlBuilder, spaceGradientProvider)
|
||||
},
|
||||
isNewButtonVisible = true
|
||||
)
|
||||
command.value = GalleryInstallationNavigation.Spaces
|
||||
},
|
||||
onFailure = { error ->
|
||||
Timber.e(error, "GetSpaceViews failed")
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun onNewSpaceClick() {
|
||||
command.value = GalleryInstallationNavigation.Dismiss
|
||||
val manifestInfo = (mainState.value as? GalleryInstallationState.Success)?.info ?: return
|
||||
val params = CreateSpace.Params(
|
||||
details = mapOf(
|
||||
Relations.NAME to manifestInfo.name,
|
||||
Relations.ICON_OPTION to spaceGradientProvider.randomId().toDouble()
|
||||
)
|
||||
)
|
||||
viewModelScope.launch {
|
||||
createSpace.async(params).fold(
|
||||
onSuccess = { space ->
|
||||
Timber.d("CreateSpace success, space: $space")
|
||||
proceedWithInstallation(
|
||||
spaceId = SpaceId(space),
|
||||
isNewSpace = true,
|
||||
manifestInfo = manifestInfo
|
||||
)
|
||||
},
|
||||
onFailure = { error ->
|
||||
Timber.e(error, "CreateSpace failed")
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun onSpaceClick(space: GallerySpaceView) {
|
||||
command.value = GalleryInstallationNavigation.Dismiss
|
||||
val manifestInfo = (mainState.value as? GalleryInstallationState.Success)?.info ?: return
|
||||
proceedWithInstallation(
|
||||
spaceId = SpaceId(space.obj.id),
|
||||
isNewSpace = false,
|
||||
manifestInfo = manifestInfo
|
||||
)
|
||||
}
|
||||
|
||||
fun onDismiss() {
|
||||
command.value = GalleryInstallationNavigation.Dismiss
|
||||
}
|
||||
|
||||
private fun proceedWithInstallation(
|
||||
spaceId: SpaceId,
|
||||
isNewSpace: Boolean,
|
||||
manifestInfo: ManifestInfo
|
||||
) {
|
||||
val params = ImportExperience.Params(
|
||||
spaceId = spaceId,
|
||||
url = manifestInfo.downloadLink,
|
||||
title = manifestInfo.title,
|
||||
isNewSpace = isNewSpace
|
||||
)
|
||||
viewModelScope.launch {
|
||||
importExperience.async(params).fold(
|
||||
onSuccess = {
|
||||
Timber.d("ObjectImportExperience success")
|
||||
command.value = GalleryInstallationNavigation.Success
|
||||
},
|
||||
onFailure = { error ->
|
||||
Timber.e(error, "ObjectImportExperience failed")
|
||||
command.value = GalleryInstallationNavigation.Error
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class ViewModelParams(
|
||||
val deepLinkType: String,
|
||||
val deepLinkSource: String
|
||||
)
|
||||
}
|
||||
|
||||
private fun ObjectWrapper.SpaceView.toView(
|
||||
builder: UrlBuilder,
|
||||
spaceGradientProvider: SpaceGradientProvider
|
||||
) = GallerySpaceView(
|
||||
obj = this,
|
||||
icon = spaceIcon(builder, spaceGradientProvider)
|
||||
)
|
|
@ -3,15 +3,35 @@ package com.anytypeio.anytype.gallery_experience.viewmodel
|
|||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.anytypeio.anytype.analytics.base.Analytics
|
||||
import com.anytypeio.anytype.domain.gallery_experience.DownloadGalleryManifest
|
||||
import com.anytypeio.anytype.domain.gallery_experience.ImportExperience
|
||||
import com.anytypeio.anytype.domain.misc.UrlBuilder
|
||||
import com.anytypeio.anytype.domain.spaces.CreateSpace
|
||||
import com.anytypeio.anytype.domain.spaces.GetSpaceViews
|
||||
import com.anytypeio.anytype.presentation.spaces.SpaceGradientProvider
|
||||
import javax.inject.Inject
|
||||
|
||||
class GalleryInstallationViewModelFactory@Inject constructor(
|
||||
class GalleryInstallationViewModelFactory @Inject constructor(
|
||||
private val viewModelParams: GalleryInstallationViewModel.ViewModelParams,
|
||||
private val downloadGalleryManifest: DownloadGalleryManifest,
|
||||
private val importExperience: ImportExperience,
|
||||
private val analytics: Analytics,
|
||||
private val getSpaceViews: GetSpaceViews,
|
||||
private val createSpace: CreateSpace,
|
||||
private val urlBuilder: UrlBuilder,
|
||||
private val spaceGradientProvider: SpaceGradientProvider
|
||||
) : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return GalleryInstallationViewModel(
|
||||
viewModelParams = viewModelParams,
|
||||
downloadGalleryManifest = downloadGalleryManifest,
|
||||
importExperience = importExperience,
|
||||
analytics = analytics,
|
||||
getSpaceViews = getSpaceViews,
|
||||
urlBuilder = urlBuilder,
|
||||
spaceGradientProvider = spaceGradientProvider,
|
||||
createSpace = createSpace
|
||||
) as T
|
||||
}
|
||||
}
|
|
@ -1434,4 +1434,10 @@
|
|||
<string name="payments_welcome_subtitle">Big cheers for your curiosity!</string>
|
||||
<string name="payments_welcome_button">Continue</string>
|
||||
|
||||
|
||||
<!-- Gallery Experience -->
|
||||
<string name="gallery_experience_made">Made by</string>
|
||||
<string name="gallery_experience_install">Install</string>
|
||||
<string name="gallery_experience_install_new">Install to new space</string>
|
||||
|
||||
</resources>
|
|
@ -12,6 +12,7 @@ import com.anytypeio.anytype.core_models.DVViewer
|
|||
import com.anytypeio.anytype.core_models.DVViewerType
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.Key
|
||||
import com.anytypeio.anytype.core_models.ManifestInfo
|
||||
import com.anytypeio.anytype.core_models.NodeUsageInfo
|
||||
import com.anytypeio.anytype.core_models.ObjectType
|
||||
import com.anytypeio.anytype.core_models.ObjectView
|
||||
|
@ -926,4 +927,12 @@ class BlockMiddleware(
|
|||
override suspend fun getSpaceInviteLink(spaceId: SpaceId): SpaceInviteLink {
|
||||
return middleware.getCurrentSpaceInvite(spaceId)
|
||||
}
|
||||
|
||||
override suspend fun downloadGalleryManifest(command: Command.DownloadGalleryManifest): ManifestInfo? {
|
||||
return middleware.downloadGalleryManifest(command)
|
||||
}
|
||||
|
||||
override suspend fun importExperience(command: Command.ImportExperience): Payload {
|
||||
return middleware.importExperience(command)
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@ import com.anytypeio.anytype.core_models.DVViewer
|
|||
import com.anytypeio.anytype.core_models.DVViewerType
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.Key
|
||||
import com.anytypeio.anytype.core_models.ManifestInfo
|
||||
import com.anytypeio.anytype.core_models.NodeUsageInfo
|
||||
import com.anytypeio.anytype.core_models.ObjectType
|
||||
import com.anytypeio.anytype.core_models.ObjectView
|
||||
|
@ -2502,6 +2503,33 @@ class Middleware @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun downloadGalleryManifest(command: Command.DownloadGalleryManifest): ManifestInfo? {
|
||||
val request = Rpc.Gallery.DownloadManifest.Request(
|
||||
url = command.url
|
||||
)
|
||||
if (BuildConfig.DEBUG) logRequest(request)
|
||||
val response = service.downloadManifest(request)
|
||||
if (BuildConfig.DEBUG) logResponse(response)
|
||||
return response.info?.toCoreModel()
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun importExperience(
|
||||
command: Command.ImportExperience
|
||||
): Payload {
|
||||
val request = Rpc.Object.ImportExperience.Request(
|
||||
spaceId = command.space.id,
|
||||
url = command.url,
|
||||
title = command.title,
|
||||
isNewSpace = command.isNewSpace
|
||||
)
|
||||
if (BuildConfig.DEBUG) logRequest(request)
|
||||
val response = service.objectImportExperience(request)
|
||||
if (BuildConfig.DEBUG) logResponse(response)
|
||||
return response.event.toPayload()
|
||||
}
|
||||
|
||||
private fun logRequest(any: Any) {
|
||||
logger.logRequest(any).also {
|
||||
if (BuildConfig.DEBUG && threadInfo.isOnMainThread()) {
|
||||
|
|
|
@ -68,3 +68,5 @@ typealias MWidgetLayout = anytype.model.Block.Content.Widget.Layout
|
|||
typealias MNetworkMode = anytype.Rpc.Account.NetworkMode
|
||||
|
||||
typealias MParticipantPermission = anytype.model.ParticipantPermissions
|
||||
|
||||
typealias MManifestInfo = anytype.model.ManifestInfo
|
||||
|
|
|
@ -23,6 +23,7 @@ import com.anytypeio.anytype.core_models.DVViewerRelation
|
|||
import com.anytypeio.anytype.core_models.DVViewerType
|
||||
import com.anytypeio.anytype.core_models.Event
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.ManifestInfo
|
||||
import com.anytypeio.anytype.core_models.NodeUsage
|
||||
import com.anytypeio.anytype.core_models.NodeUsageInfo
|
||||
import com.anytypeio.anytype.core_models.ObjectOrder
|
||||
|
@ -789,4 +790,21 @@ fun Account.Info.config() : Config = Config(
|
|||
analytics = analyticsId,
|
||||
device = deviceId,
|
||||
network = networkId
|
||||
)
|
||||
)
|
||||
|
||||
fun MManifestInfo.toCoreModel(): ManifestInfo {
|
||||
return ManifestInfo(
|
||||
schema = schema,
|
||||
id = id,
|
||||
name = name,
|
||||
author = author,
|
||||
license = license,
|
||||
title = title,
|
||||
description = description,
|
||||
screenshots = screenshots,
|
||||
downloadLink = downloadLink,
|
||||
fileSize = fileSize,
|
||||
categories = categories,
|
||||
language = language
|
||||
)
|
||||
}
|
|
@ -522,4 +522,12 @@ interface MiddlewareService {
|
|||
fun spaceStopSharing(request: Rpc.Space.StopSharing.Request): Rpc.Space.StopSharing.Response
|
||||
|
||||
//endregion
|
||||
|
||||
//region GALLERY EXPERIENCE
|
||||
@Throws(Exception::class)
|
||||
fun downloadManifest(request: Rpc.Gallery.DownloadManifest.Request): Rpc.Gallery.DownloadManifest.Response
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun objectImportExperience(request: Rpc.Object.ImportExperience.Request): Rpc.Object.ImportExperience.Response
|
||||
//endregion
|
||||
}
|
|
@ -1919,4 +1919,30 @@ class MiddlewareServiceImplementation @Inject constructor(
|
|||
return response
|
||||
}
|
||||
}
|
||||
|
||||
override fun downloadManifest(request: Rpc.Gallery.DownloadManifest.Request): Rpc.Gallery.DownloadManifest.Response {
|
||||
val encoded = Service.galleryDownloadManifest(
|
||||
Rpc.Gallery.DownloadManifest.Request.ADAPTER.encode(request)
|
||||
)
|
||||
val response = Rpc.Gallery.DownloadManifest.Response.ADAPTER.decode(encoded)
|
||||
val error = response.error
|
||||
if (error != null && error.code != Rpc.Gallery.DownloadManifest.Response.Error.Code.NULL) {
|
||||
throw Exception(error.description)
|
||||
} else {
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
override fun objectImportExperience(request: Rpc.Object.ImportExperience.Request): Rpc.Object.ImportExperience.Response {
|
||||
val encoded = Service.objectImportExperience(
|
||||
Rpc.Object.ImportExperience.Request.ADAPTER.encode(request)
|
||||
)
|
||||
val response = Rpc.Object.ImportExperience.Response.ADAPTER.decode(encoded)
|
||||
val error = response.error
|
||||
if (error != null && error.code != Rpc.Object.ImportExperience.Response.Error.Code.NULL) {
|
||||
throw Exception(error.description)
|
||||
} else {
|
||||
return response
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1047,13 +1047,18 @@ class HomeScreenViewModel(
|
|||
|
||||
fun onResume(deeplink: DeepLinkResolver.Action? = null) {
|
||||
Timber.d("onResume, deeplink: ${deeplink}")
|
||||
when(deeplink) {
|
||||
when (deeplink) {
|
||||
is DeepLinkResolver.Action.Import.Experience -> {
|
||||
viewModelScope.launch {
|
||||
delay(1000)
|
||||
commands.emit(Command.Deeplink.CannotImportExperience)
|
||||
commands.emit(
|
||||
Command.Deeplink.GalleryInstallation(
|
||||
deepLinkType = deeplink.type,
|
||||
deepLinkSource = deeplink.source
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is DeepLinkResolver.Action.Invite -> {
|
||||
viewModelScope.launch {
|
||||
delay(1000)
|
||||
|
@ -1574,6 +1579,10 @@ sealed class Command {
|
|||
sealed class Deeplink : Command() {
|
||||
object CannotImportExperience : Deeplink()
|
||||
data class Invite(val link: String) : Deeplink()
|
||||
data class GalleryInstallation(
|
||||
val deepLinkType: String,
|
||||
val deepLinkSource: String
|
||||
) : Deeplink()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue