mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-08 05:47:05 +09:00
DROID-1776 Gallery experience | Installation process + notification handling (#1022)
This commit is contained in:
parent
7349cfe901
commit
fd94eba92c
12 changed files with 108 additions and 40 deletions
|
@ -7,6 +7,8 @@ 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.domain.multiplayer.UserPermissionProvider
|
||||
import com.anytypeio.anytype.domain.workspace.EventProcessChannel
|
||||
import com.anytypeio.anytype.gallery_experience.viewmodel.GalleryInstallationViewModel
|
||||
import com.anytypeio.anytype.gallery_experience.viewmodel.GalleryInstallationViewModelFactory
|
||||
import com.anytypeio.anytype.presentation.spaces.SpaceGradientProvider
|
||||
|
@ -65,4 +67,6 @@ interface GalleryInstallationComponentDependencies : ComponentDependencies {
|
|||
fun appCoroutineDispatchers(): AppCoroutineDispatchers
|
||||
fun analytics(): Analytics
|
||||
fun urlBuilder(): UrlBuilder
|
||||
fun userPermissionProvider(): UserPermissionProvider
|
||||
fun eventProcessChannel(): EventProcessChannel
|
||||
}
|
|
@ -30,6 +30,7 @@ import com.google.accompanist.navigation.material.ModalBottomSheetLayout
|
|||
import com.google.accompanist.navigation.material.bottomSheet
|
||||
import com.google.accompanist.navigation.material.rememberBottomSheetNavigator
|
||||
import javax.inject.Inject
|
||||
import timber.log.Timber
|
||||
|
||||
class GalleryInstallationFragment : BaseBottomSheetComposeFragment() {
|
||||
|
||||
|
@ -62,6 +63,7 @@ class GalleryInstallationFragment : BaseBottomSheetComposeFragment() {
|
|||
override fun onStart() {
|
||||
super.onStart()
|
||||
jobs += subscribe(vm.command) { command ->
|
||||
Timber.d("GalleryInstallationFragment command: $command")
|
||||
when (command) {
|
||||
GalleryInstallationNavigation.Main -> navController.navigate(
|
||||
GalleryInstallationNavigation.Main.route
|
||||
|
@ -70,6 +72,7 @@ class GalleryInstallationFragment : BaseBottomSheetComposeFragment() {
|
|||
GalleryInstallationNavigation.Spaces.route
|
||||
)
|
||||
GalleryInstallationNavigation.Dismiss -> navController.popBackStack()
|
||||
GalleryInstallationNavigation.Exit -> dismiss()
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -970,7 +970,7 @@ class BlockDataRepository(
|
|||
return remote.downloadGalleryManifest(command)
|
||||
}
|
||||
|
||||
override suspend fun importExperience(command: Command.ImportExperience): Payload {
|
||||
return remote.importExperience(command)
|
||||
override suspend fun importExperience(command: Command.ImportExperience) {
|
||||
remote.importExperience(command)
|
||||
}
|
||||
}
|
|
@ -415,5 +415,5 @@ interface BlockRemote {
|
|||
suspend fun getSpaceInviteLink(spaceId: SpaceId): SpaceInviteLink
|
||||
|
||||
suspend fun downloadGalleryManifest(command: Command.DownloadGalleryManifest): ManifestInfo?
|
||||
suspend fun importExperience(command: Command.ImportExperience): Payload
|
||||
suspend fun importExperience(command: Command.ImportExperience)
|
||||
}
|
|
@ -458,5 +458,5 @@ interface BlockRepository {
|
|||
suspend fun getSpaceInviteLink(spaceId: SpaceId): SpaceInviteLink
|
||||
|
||||
suspend fun downloadGalleryManifest(command: Command.DownloadGalleryManifest): ManifestInfo?
|
||||
suspend fun importExperience(command: Command.ImportExperience): Payload
|
||||
suspend fun importExperience(command: Command.ImportExperience)
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
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
|
||||
|
@ -11,16 +10,16 @@ import javax.inject.Inject
|
|||
class ImportExperience @Inject constructor(
|
||||
dispatchers: AppCoroutineDispatchers,
|
||||
private val repo: BlockRepository
|
||||
) : ResultInteractor<ImportExperience.Params, Payload>(dispatchers.io) {
|
||||
) : ResultInteractor<ImportExperience.Params, Unit>(dispatchers.io) {
|
||||
|
||||
override suspend fun doWork(params: Params): Payload {
|
||||
override suspend fun doWork(params: Params) {
|
||||
val command = Command.ImportExperience(
|
||||
space = params.spaceId,
|
||||
url = params.url,
|
||||
title = params.title,
|
||||
isNewSpace = params.isNewSpace
|
||||
)
|
||||
return repo.importExperience(command)
|
||||
repo.importExperience(command)
|
||||
}
|
||||
|
||||
data class Params(
|
||||
|
|
|
@ -7,7 +7,7 @@ import com.anytypeio.anytype.presentation.spaces.SpaceIconView
|
|||
sealed class GalleryInstallationState {
|
||||
object Hidden : GalleryInstallationState()
|
||||
object Loading : GalleryInstallationState()
|
||||
data class Success(val info: ManifestInfo) : GalleryInstallationState()
|
||||
data class Success(val info: ManifestInfo, val isLoading: Boolean = false) : GalleryInstallationState()
|
||||
}
|
||||
|
||||
data class GalleryInstallationSpacesState(
|
||||
|
@ -21,6 +21,7 @@ sealed class GalleryInstallationNavigation(val route: String) {
|
|||
object Success : GalleryInstallationNavigation("success")
|
||||
object Error : GalleryInstallationNavigation("error")
|
||||
object Dismiss : GalleryInstallationNavigation("")
|
||||
object Exit : GalleryInstallationNavigation("exit")
|
||||
}
|
||||
|
||||
data class GallerySpaceView(
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package com.anytypeio.anytype.gallery_experience.screens
|
||||
|
||||
import androidx.compose.animation.core.LinearEasing
|
||||
import androidx.compose.animation.core.RepeatMode
|
||||
import androidx.compose.animation.core.animateFloat
|
||||
import androidx.compose.animation.core.infiniteRepeatable
|
||||
|
@ -30,8 +29,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.shadow
|
||||
|
@ -49,7 +46,7 @@ import androidx.compose.ui.unit.dp
|
|||
import coil.compose.AsyncImage
|
||||
import com.anytypeio.anytype.core_models.ManifestInfo
|
||||
import com.anytypeio.anytype.core_ui.R
|
||||
import com.anytypeio.anytype.core_ui.views.ButtonPrimary
|
||||
import com.anytypeio.anytype.core_ui.views.ButtonPrimaryLoading
|
||||
import com.anytypeio.anytype.core_ui.views.ButtonSize
|
||||
import com.anytypeio.anytype.core_ui.views.Caption1Medium
|
||||
import com.anytypeio.anytype.core_ui.views.Caption1Regular
|
||||
|
@ -62,13 +59,19 @@ fun GalleryInstallationScreen(
|
|||
state: GalleryInstallationState,
|
||||
onInstallClicked: () -> Unit
|
||||
) {
|
||||
val brush = Brush.verticalGradient(
|
||||
listOf(
|
||||
colorResource(id = R.color.background_highlighted),
|
||||
Color.Transparent
|
||||
)
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.nestedScroll(rememberNestedScrollInteropConnection())
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.background(
|
||||
color = colorResource(id = R.color.background_secondary),
|
||||
brush = brush,
|
||||
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)
|
||||
),
|
||||
) {
|
||||
|
@ -83,6 +86,7 @@ fun GalleryInstallationScreen(
|
|||
GalleryInstallationState.Loading -> {
|
||||
LoadingScreen()
|
||||
}
|
||||
|
||||
is GalleryInstallationState.Success -> {
|
||||
SuccessScreen(state, onInstallClicked)
|
||||
}
|
||||
|
@ -94,25 +98,26 @@ fun GalleryInstallationScreen(
|
|||
@Composable
|
||||
private fun LoadingScreen() {
|
||||
val infiniteTransition = rememberInfiniteTransition(label = "")
|
||||
val translateAnim by infiniteTransition.animateFloat(
|
||||
|
||||
val colorStart = colorResource(id = R.color.background_secondary)
|
||||
val colorEnd = colorResource(id = R.color.shape_secondary)
|
||||
val shimmerColorShades = listOf(
|
||||
colorStart,
|
||||
colorEnd,
|
||||
colorStart
|
||||
)
|
||||
val shimmerAnimation = infiniteTransition.animateFloat(
|
||||
initialValue = 0f,
|
||||
targetValue = 1000f,
|
||||
animationSpec = infiniteRepeatable(
|
||||
animation = tween(
|
||||
durationMillis = 200,
|
||||
easing = LinearEasing
|
||||
),
|
||||
animation = tween(durationMillis = 1000),
|
||||
repeatMode = RepeatMode.Restart
|
||||
), label = ""
|
||||
)
|
||||
val brush = Brush.linearGradient(
|
||||
colors = listOf(
|
||||
Color(0xFFF5F5F5),
|
||||
Color(0xFFEBEBEB),
|
||||
Color(0xFFF5F5F5)
|
||||
),
|
||||
start = Offset(10f, 10f),
|
||||
end = Offset(translateAnim, translateAnim)
|
||||
colors = shimmerColorShades,
|
||||
start = Offset.Zero,
|
||||
end = Offset(x = shimmerAnimation.value, y = 0F)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Box(
|
||||
|
@ -172,7 +177,11 @@ private fun SuccessScreen(
|
|||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(220.dp)
|
||||
.padding(horizontal = 20.dp),
|
||||
.padding(horizontal = 20.dp)
|
||||
.background(
|
||||
color = colorResource(id = R.color.background_primary),
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
),
|
||||
state = pagerState
|
||||
) { index ->
|
||||
val screenshotUrl = state.info.screenshots[index]
|
||||
|
@ -247,8 +256,9 @@ private fun SuccessScreen(
|
|||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(39.dp))
|
||||
ButtonPrimary(
|
||||
modifier = Modifier
|
||||
ButtonPrimaryLoading(
|
||||
loading = state.isLoading,
|
||||
modifierButton = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 20.dp),
|
||||
onClick = { onInstallClicked() },
|
||||
|
@ -292,7 +302,14 @@ private fun GalleryInstallationScreenPreview() {
|
|||
screenshots = listOf("1", "2", "3", "4"),
|
||||
downloadLink = "lobortis",
|
||||
fileSize = 1213,
|
||||
categories = listOf("tag1", "tag2", "tag3312212112", "tag421312312", "tag5", "tag6"),
|
||||
categories = listOf(
|
||||
"tag1",
|
||||
"tag2",
|
||||
"tag3312212112",
|
||||
"tag421312312",
|
||||
"tag5",
|
||||
"tag6"
|
||||
),
|
||||
language = "nisi"
|
||||
)
|
||||
)
|
||||
|
|
|
@ -5,14 +5,17 @@ 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.Process
|
||||
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.multiplayer.UserPermissionProvider
|
||||
import com.anytypeio.anytype.domain.spaces.CreateSpace
|
||||
import com.anytypeio.anytype.domain.spaces.GetSpaceViews
|
||||
import com.anytypeio.anytype.domain.workspace.EventProcessChannel
|
||||
import com.anytypeio.anytype.gallery_experience.models.GalleryInstallationNavigation
|
||||
import com.anytypeio.anytype.gallery_experience.models.GalleryInstallationSpacesState
|
||||
import com.anytypeio.anytype.gallery_experience.models.GalleryInstallationState
|
||||
|
@ -31,7 +34,9 @@ class GalleryInstallationViewModel(
|
|||
private val getSpaceViews: GetSpaceViews,
|
||||
private val createSpace: CreateSpace,
|
||||
private val urlBuilder: UrlBuilder,
|
||||
private val spaceGradientProvider: SpaceGradientProvider
|
||||
private val spaceGradientProvider: SpaceGradientProvider,
|
||||
private val userPermissionProvider: UserPermissionProvider,
|
||||
private val eventProcessChannel: EventProcessChannel
|
||||
) : ViewModel() {
|
||||
|
||||
val mainState = MutableStateFlow<GalleryInstallationState>(GalleryInstallationState.Loading)
|
||||
|
@ -39,6 +44,8 @@ class GalleryInstallationViewModel(
|
|||
MutableStateFlow(GalleryInstallationSpacesState(emptyList(), false))
|
||||
val command = MutableStateFlow<GalleryInstallationNavigation?>(null)
|
||||
|
||||
private val MAX_SPACES = 10
|
||||
|
||||
init {
|
||||
Timber.d("GalleryInstallationViewModel init, viewModelParams: $viewModelParams")
|
||||
downloadGalleryManifest()
|
||||
|
@ -70,11 +77,12 @@ class GalleryInstallationViewModel(
|
|||
getSpaceViews.async(Unit).fold(
|
||||
onSuccess = { spaces ->
|
||||
Timber.d("GetSpaceViews success, spaceViews: $spaces")
|
||||
val filteredSpaces = filterSpacesByPermissions(spaces)
|
||||
spacesViewState.value = GalleryInstallationSpacesState(
|
||||
spaces = spaces.map {
|
||||
spaces = filteredSpaces.map {
|
||||
it.toView(urlBuilder, spaceGradientProvider)
|
||||
},
|
||||
isNewButtonVisible = true
|
||||
isNewButtonVisible = filteredSpaces.size < MAX_SPACES
|
||||
)
|
||||
command.value = GalleryInstallationNavigation.Spaces
|
||||
},
|
||||
|
@ -86,8 +94,11 @@ class GalleryInstallationViewModel(
|
|||
}
|
||||
|
||||
fun onNewSpaceClick() {
|
||||
subscribeToEventProcessChannel()
|
||||
command.value = GalleryInstallationNavigation.Dismiss
|
||||
val manifestInfo = (mainState.value as? GalleryInstallationState.Success)?.info ?: return
|
||||
mainState.value =
|
||||
(mainState.value as? GalleryInstallationState.Success)?.copy(isLoading = true) ?: return
|
||||
val params = CreateSpace.Params(
|
||||
details = mapOf(
|
||||
Relations.NAME to manifestInfo.name,
|
||||
|
@ -112,10 +123,19 @@ class GalleryInstallationViewModel(
|
|||
}
|
||||
|
||||
fun onSpaceClick(space: GallerySpaceView) {
|
||||
subscribeToEventProcessChannel()
|
||||
Timber.d("onSpaceClick, space: $space")
|
||||
command.value = GalleryInstallationNavigation.Dismiss
|
||||
mainState.value =
|
||||
(mainState.value as? GalleryInstallationState.Success)?.copy(isLoading = true) ?: return
|
||||
val manifestInfo = (mainState.value as? GalleryInstallationState.Success)?.info ?: return
|
||||
val spaceId = space.obj.targetSpaceId
|
||||
if (spaceId == null) {
|
||||
Timber.e("onSpaceClick, spaceId is null")
|
||||
return
|
||||
}
|
||||
proceedWithInstallation(
|
||||
spaceId = SpaceId(space.obj.id),
|
||||
spaceId = SpaceId(spaceId),
|
||||
isNewSpace = false,
|
||||
manifestInfo = manifestInfo
|
||||
)
|
||||
|
@ -150,6 +170,25 @@ class GalleryInstallationViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
private fun subscribeToEventProcessChannel() {
|
||||
viewModelScope.launch {
|
||||
eventProcessChannel.observe().collect { events ->
|
||||
Timber.d("EventProcessChannel events: $events")
|
||||
if (events.any { it is Process.Event.Done && it.process?.type == Process.Type.IMPORT }) {
|
||||
command.value = GalleryInstallationNavigation.Exit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun filterSpacesByPermissions(spaces: List<ObjectWrapper.SpaceView>): List<ObjectWrapper.SpaceView> {
|
||||
return spaces.filter {
|
||||
val targetSpaceId = it.targetSpaceId ?: return@filter false
|
||||
val userPermissions = userPermissionProvider.get(SpaceId(targetSpaceId))
|
||||
userPermissions?.isOwnerOrEditor() == true
|
||||
}
|
||||
}
|
||||
|
||||
data class ViewModelParams(
|
||||
val deepLinkType: String,
|
||||
val deepLinkSource: String
|
||||
|
|
|
@ -6,8 +6,10 @@ 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.multiplayer.UserPermissionProvider
|
||||
import com.anytypeio.anytype.domain.spaces.CreateSpace
|
||||
import com.anytypeio.anytype.domain.spaces.GetSpaceViews
|
||||
import com.anytypeio.anytype.domain.workspace.EventProcessChannel
|
||||
import com.anytypeio.anytype.presentation.spaces.SpaceGradientProvider
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -19,7 +21,9 @@ class GalleryInstallationViewModelFactory @Inject constructor(
|
|||
private val getSpaceViews: GetSpaceViews,
|
||||
private val createSpace: CreateSpace,
|
||||
private val urlBuilder: UrlBuilder,
|
||||
private val spaceGradientProvider: SpaceGradientProvider
|
||||
private val spaceGradientProvider: SpaceGradientProvider,
|
||||
private val userPermissionProvider: UserPermissionProvider,
|
||||
private val eventProcessChannel: EventProcessChannel
|
||||
) : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
|
@ -31,7 +35,9 @@ class GalleryInstallationViewModelFactory @Inject constructor(
|
|||
getSpaceViews = getSpaceViews,
|
||||
urlBuilder = urlBuilder,
|
||||
spaceGradientProvider = spaceGradientProvider,
|
||||
createSpace = createSpace
|
||||
createSpace = createSpace,
|
||||
userPermissionProvider = userPermissionProvider,
|
||||
eventProcessChannel = eventProcessChannel
|
||||
) as T
|
||||
}
|
||||
}
|
|
@ -932,7 +932,7 @@ class BlockMiddleware(
|
|||
return middleware.downloadGalleryManifest(command)
|
||||
}
|
||||
|
||||
override suspend fun importExperience(command: Command.ImportExperience): Payload {
|
||||
return middleware.importExperience(command)
|
||||
override suspend fun importExperience(command: Command.ImportExperience) {
|
||||
middleware.importExperience(command)
|
||||
}
|
||||
}
|
|
@ -2517,7 +2517,7 @@ class Middleware @Inject constructor(
|
|||
@Throws(Exception::class)
|
||||
fun importExperience(
|
||||
command: Command.ImportExperience
|
||||
): Payload {
|
||||
) {
|
||||
val request = Rpc.Object.ImportExperience.Request(
|
||||
spaceId = command.space.id,
|
||||
url = command.url,
|
||||
|
@ -2527,7 +2527,6 @@ class Middleware @Inject constructor(
|
|||
if (BuildConfig.DEBUG) logRequest(request)
|
||||
val response = service.objectImportExperience(request)
|
||||
if (BuildConfig.DEBUG) logResponse(response)
|
||||
return response.event.toPayload()
|
||||
}
|
||||
|
||||
private fun logRequest(any: Any) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue