mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-08 05:47:05 +09:00
DROID-2550 Sharing extension | FileDrop command implementation (#1362)
This commit is contained in:
parent
986155f10a
commit
098a6e7146
15 changed files with 609 additions and 153 deletions
|
@ -3,6 +3,8 @@ package com.anytypeio.anytype.di.feature.sharing
|
|||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.anytypeio.anytype.analytics.base.Analytics
|
||||
import com.anytypeio.anytype.core_utils.di.scope.PerDialog
|
||||
import com.anytypeio.anytype.data.auth.event.EventProcessDropFilesDateChannel
|
||||
import com.anytypeio.anytype.data.auth.event.EventProcessDropFilesRemoteChannel
|
||||
import com.anytypeio.anytype.di.common.ComponentDependencies
|
||||
import com.anytypeio.anytype.domain.account.AwaitAccountStartManager
|
||||
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
|
||||
|
@ -13,13 +15,17 @@ import com.anytypeio.anytype.domain.device.FileSharer
|
|||
import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer
|
||||
import com.anytypeio.anytype.domain.misc.UrlBuilder
|
||||
import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider
|
||||
import com.anytypeio.anytype.domain.workspace.EventProcessDropFilesChannel
|
||||
import com.anytypeio.anytype.domain.workspace.SpaceManager
|
||||
import com.anytypeio.anytype.middleware.EventProxy
|
||||
import com.anytypeio.anytype.middleware.interactor.EventProcessDropFilesMiddlewareChannel
|
||||
import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate
|
||||
import com.anytypeio.anytype.presentation.sharing.AddToAnytypeViewModel
|
||||
import com.anytypeio.anytype.ui.sharing.SharingFragment
|
||||
import dagger.Binds
|
||||
import dagger.Component
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
||||
@Component(
|
||||
dependencies = [AddToAnytypeDependencies::class],
|
||||
|
@ -40,6 +46,19 @@ interface AddToAnytypeComponent {
|
|||
|
||||
@Module
|
||||
object AddToAnytypeModule {
|
||||
|
||||
@Provides
|
||||
@PerDialog
|
||||
fun provideEventProcessRemoteChannel(
|
||||
proxy: EventProxy
|
||||
): EventProcessDropFilesRemoteChannel = EventProcessDropFilesMiddlewareChannel(events = proxy)
|
||||
|
||||
@Provides
|
||||
@PerDialog
|
||||
fun provideEventProcessDateChannel(
|
||||
channel: EventProcessDropFilesRemoteChannel
|
||||
): EventProcessDropFilesChannel = EventProcessDropFilesDateChannel(channel = channel)
|
||||
|
||||
@Module
|
||||
interface Declarations {
|
||||
@PerDialog
|
||||
|
@ -61,4 +80,5 @@ interface AddToAnytypeDependencies : ComponentDependencies {
|
|||
fun fileSharer(): FileSharer
|
||||
fun permissions(): UserPermissionProvider
|
||||
fun analyticSpaceHelper(): AnalyticSpaceHelperDelegate
|
||||
fun eventProxy(): EventProxy
|
||||
}
|
|
@ -1,5 +1,12 @@
|
|||
package com.anytypeio.anytype.ui.sharing
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
|
@ -12,10 +19,15 @@ import androidx.compose.foundation.layout.height
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.DropdownMenuItem
|
||||
import androidx.compose.material.LinearProgressIndicator
|
||||
import androidx.compose.material.ProgressIndicatorDefaults
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.runtime.Composable
|
||||
|
@ -28,6 +40,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
|
@ -37,28 +50,44 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.core.graphics.toColorInt
|
||||
import coil.compose.rememberAsyncImagePainter
|
||||
import com.anytypeio.anytype.R
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.ObjectWrapper
|
||||
import com.anytypeio.anytype.core_ui.foundation.noRippleClickable
|
||||
import com.anytypeio.anytype.core_ui.views.BodyRegular
|
||||
import com.anytypeio.anytype.core_ui.views.ButtonPrimary
|
||||
import com.anytypeio.anytype.core_ui.views.ButtonPrimaryLoading
|
||||
import com.anytypeio.anytype.core_ui.views.ButtonSecondary
|
||||
import com.anytypeio.anytype.core_ui.views.ButtonSize
|
||||
import com.anytypeio.anytype.core_ui.views.Caption1Medium
|
||||
import com.anytypeio.anytype.core_ui.views.TitleInter15
|
||||
import com.anytypeio.anytype.core_ui.views.Title2
|
||||
import com.anytypeio.anytype.core_utils.ui.MultipleEventCutter
|
||||
import com.anytypeio.anytype.core_utils.ui.get
|
||||
import com.anytypeio.anytype.presentation.sharing.AddToAnytypeViewModel
|
||||
import com.anytypeio.anytype.presentation.sharing.AddToAnytypeViewModel.SpaceView
|
||||
import com.anytypeio.anytype.presentation.spaces.SpaceIconView
|
||||
|
||||
@Preview
|
||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES, name = "Light Mode")
|
||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO, name = "Dark Mode")
|
||||
@Composable
|
||||
fun AddToAnytypeScreenUrlPreview() {
|
||||
AddToAnytypeScreen(
|
||||
data = SharingData.Url("https://en.wikipedia.org/wiki/Walter_Benjamin"),
|
||||
onCancelClicked = {},
|
||||
onDoneClicked = {},
|
||||
spaces = emptyList(),
|
||||
onAddClicked = {},
|
||||
spaces = listOf(
|
||||
SpaceView(
|
||||
obj = ObjectWrapper.SpaceView(map = mapOf("name" to "Space 1")),
|
||||
isSelected = true,
|
||||
icon = SpaceIconView.Gradient(from = "#FF0000", to = "#00FF00")
|
||||
)
|
||||
),
|
||||
onSelectSpaceClicked = {},
|
||||
content = "https://en.wikipedia.org/wiki/Walter_Benjamin"
|
||||
onOpenClicked = {},
|
||||
content = "https://en.wikipedia.org/wiki/Walter_Benjamin",
|
||||
progressState = AddToAnytypeViewModel.ProgressState.Done(""),
|
||||
onCancelProcessClicked = {}
|
||||
//progressState = AddToAnytypeViewModel.ProgressState.Error(" I understand that contributing to this repository will require me to agree with the CLA I understand that contributing to this repository will require me to agree with the CLA\n")
|
||||
//progressState = AddToAnytypeViewModel.ProgressState.Progress(processId = "dasda", progress = 0.8f)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -68,10 +97,23 @@ fun AddToAnytypeScreenNotePreview() {
|
|||
AddToAnytypeScreen(
|
||||
data = SharingData.Text("The Work of Art in the Age of its Technological Reproducibility"),
|
||||
onCancelClicked = {},
|
||||
onDoneClicked = {},
|
||||
spaces = emptyList(),
|
||||
onAddClicked = {},
|
||||
spaces = listOf(
|
||||
SpaceView(
|
||||
obj = ObjectWrapper.SpaceView(map = mapOf()),
|
||||
isSelected = false,
|
||||
icon = SpaceIconView.Gradient(from = "#FF0000", to = "#00FF00")
|
||||
)
|
||||
),
|
||||
onSelectSpaceClicked = {},
|
||||
content = ""
|
||||
content = "",
|
||||
progressState = AddToAnytypeViewModel.ProgressState.Progress(
|
||||
processId = "dasda",
|
||||
progress = 0.8f,
|
||||
wrapperObjId = ""
|
||||
),
|
||||
onOpenClicked = {},
|
||||
onCancelProcessClicked = {}
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -80,9 +122,12 @@ fun AddToAnytypeScreen(
|
|||
content: String,
|
||||
spaces: List<SpaceView>,
|
||||
data: SharingData,
|
||||
progressState: AddToAnytypeViewModel.ProgressState,
|
||||
onCancelClicked: () -> Unit,
|
||||
onDoneClicked: (SaveAsOption) -> Unit,
|
||||
onSelectSpaceClicked: (SpaceView) -> Unit
|
||||
onCancelProcessClicked: (Id) -> Unit,
|
||||
onAddClicked: (SaveAsOption) -> Unit,
|
||||
onSelectSpaceClicked: (SpaceView) -> Unit,
|
||||
onOpenClicked: (Id) -> Unit
|
||||
) {
|
||||
var isSaveAsMenuExpanded by remember { mutableStateOf(false) }
|
||||
val items = when (data) {
|
||||
|
@ -95,7 +140,7 @@ fun AddToAnytypeScreen(
|
|||
}
|
||||
var selectedIndex by remember {
|
||||
mutableStateOf(
|
||||
when(data) {
|
||||
when (data) {
|
||||
is SharingData.Url -> SAVE_AS_BOOKMARK
|
||||
is SharingData.Image -> SAVE_AS_IMAGE
|
||||
is SharingData.File -> SAVE_AS_FILE
|
||||
|
@ -112,10 +157,9 @@ fun AddToAnytypeScreen(
|
|||
}
|
||||
Header()
|
||||
DataSection(content)
|
||||
Box(
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(76.dp)
|
||||
.noRippleClickable {
|
||||
throttler.processEvent {
|
||||
isSaveAsMenuExpanded = !isSaveAsMenuExpanded
|
||||
|
@ -140,8 +184,7 @@ fun AddToAnytypeScreen(
|
|||
else -> stringResource(id = R.string.sharing_menu_save_as_note_option)
|
||||
},
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomStart)
|
||||
.padding(bottom = 14.dp, start = 20.dp),
|
||||
.padding(top = 6.dp, start = 20.dp, bottom = 14.dp),
|
||||
style = BodyRegular,
|
||||
color = colorResource(id = R.color.text_primary)
|
||||
)
|
||||
|
@ -164,7 +207,7 @@ fun AddToAnytypeScreen(
|
|||
isSaveAsMenuExpanded = false
|
||||
}
|
||||
) {
|
||||
when(s) {
|
||||
when (s) {
|
||||
SAVE_AS_BOOKMARK -> {
|
||||
Text(
|
||||
text = stringResource(id = R.string.sharing_menu_save_as_bookmark_option),
|
||||
|
@ -172,6 +215,7 @@ fun AddToAnytypeScreen(
|
|||
color = colorResource(id = R.color.text_primary)
|
||||
)
|
||||
}
|
||||
|
||||
SAVE_AS_NOTE -> {
|
||||
Text(
|
||||
text = stringResource(id = R.string.sharing_menu_save_as_note_option),
|
||||
|
@ -179,6 +223,7 @@ fun AddToAnytypeScreen(
|
|||
color = colorResource(id = R.color.text_primary)
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
// Draw nothing
|
||||
}
|
||||
|
@ -194,6 +239,7 @@ fun AddToAnytypeScreen(
|
|||
}
|
||||
}
|
||||
}
|
||||
com.anytypeio.anytype.core_ui.foundation.Divider(paddingEnd = 20.dp, paddingStart = 20.dp)
|
||||
val selected = spaces.firstOrNull { it.isSelected }
|
||||
if (selected != null) {
|
||||
CurrentSpaceSection(
|
||||
|
@ -209,11 +255,116 @@ fun AddToAnytypeScreen(
|
|||
onSelectSpaceClicked = onSelectSpaceClicked
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
Buttons(onCancelClicked, onDoneClicked, selectedIndex)
|
||||
DefaultLinearProgressIndicator(progressState = progressState)
|
||||
when (progressState) {
|
||||
is AddToAnytypeViewModel.ProgressState.Done -> {
|
||||
ButtonsDone(
|
||||
progressState = progressState,
|
||||
onCancelClicked = onCancelClicked,
|
||||
onOpenClicked = onOpenClicked
|
||||
)
|
||||
}
|
||||
is AddToAnytypeViewModel.ProgressState.Error -> {
|
||||
Buttons(
|
||||
onCancelClicked = onCancelClicked,
|
||||
selectedIndex = selectedIndex,
|
||||
progressState = progressState,
|
||||
onAddClicked = onAddClicked
|
||||
)
|
||||
}
|
||||
AddToAnytypeViewModel.ProgressState.Init -> {
|
||||
Buttons(
|
||||
onCancelClicked = onCancelClicked,
|
||||
selectedIndex = selectedIndex,
|
||||
progressState = progressState,
|
||||
onAddClicked = onAddClicked
|
||||
)
|
||||
}
|
||||
is AddToAnytypeViewModel.ProgressState.Progress -> {
|
||||
ButtonsProgress(
|
||||
onCancelProcessClicked = onCancelProcessClicked,
|
||||
progressState = progressState,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DefaultLinearProgressIndicator(progressState: AddToAnytypeViewModel.ProgressState) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(46.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
val visible = progressState is AddToAnytypeViewModel.ProgressState.Progress
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
modifier = Modifier,
|
||||
enter = fadeIn() + slideInVertically { it },
|
||||
exit = fadeOut() + slideOutVertically { it }
|
||||
) {
|
||||
if (progressState is AddToAnytypeViewModel.ProgressState.Progress) {
|
||||
Indicator(progress = progressState.progress)
|
||||
}
|
||||
}
|
||||
val doneVisibility = progressState is AddToAnytypeViewModel.ProgressState.Done
|
||||
AnimatedVisibility(
|
||||
visible = doneVisibility,
|
||||
modifier = Modifier,
|
||||
enter = fadeIn() + slideInVertically { it },
|
||||
exit = fadeOut() + slideOutVertically { it }
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.sharing_menu_add_to_anytype_success),
|
||||
style = Caption1Medium,
|
||||
color = colorResource(id = R.color.palette_system_green),
|
||||
modifier = Modifier.padding(top = 4.dp, start = 20.dp, end = 20.dp)
|
||||
)
|
||||
}
|
||||
val errorVisible = progressState is AddToAnytypeViewModel.ProgressState.Error
|
||||
AnimatedVisibility(
|
||||
visible = errorVisible,
|
||||
modifier = Modifier,
|
||||
enter = fadeIn() + slideInVertically { it },
|
||||
exit = fadeOut() + slideOutVertically { it }
|
||||
) {
|
||||
if (progressState is AddToAnytypeViewModel.ProgressState.Error) {
|
||||
Text(
|
||||
text = stringResource(
|
||||
id = R.string.sharing_menu_add_to_anytype_error,
|
||||
progressState.error
|
||||
),
|
||||
style = Caption1Medium,
|
||||
maxLines = 2,
|
||||
color = colorResource(id = R.color.palette_dark_red),
|
||||
modifier = Modifier.padding(horizontal = 20.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Indicator(progress: Float) {
|
||||
val animatedProgress = animateFloatAsState(
|
||||
targetValue = progress,
|
||||
animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec,
|
||||
label = ""
|
||||
).value
|
||||
LinearProgressIndicator(
|
||||
progress = animatedProgress,
|
||||
color = colorResource(id = R.color.text_primary),
|
||||
modifier = Modifier
|
||||
.height(6.dp)
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 20.dp),
|
||||
backgroundColor = colorResource(id = R.color.shape_tertiary),
|
||||
strokeCap = StrokeCap.Round
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DataSection(content: String) {
|
||||
Box(
|
||||
|
@ -241,22 +392,24 @@ private fun DataSection(content: String) {
|
|||
text = content,
|
||||
style = BodyRegular,
|
||||
color = colorResource(id = R.color.text_primary),
|
||||
modifier = Modifier.padding(
|
||||
top = 30.dp,
|
||||
start = 16.dp,
|
||||
end = 16.dp,
|
||||
bottom = 10.dp
|
||||
),
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
top = 30.dp,
|
||||
start = 16.dp,
|
||||
end = 16.dp,
|
||||
bottom = 10.dp
|
||||
)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
maxLines = 5
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Buttons(
|
||||
private fun ButtonsDone(
|
||||
progressState: AddToAnytypeViewModel.ProgressState.Done,
|
||||
onCancelClicked: () -> Unit,
|
||||
onDoneClicked: (SaveAsOption) -> Unit,
|
||||
selectedIndex: Int
|
||||
onOpenClicked: (Id) -> Unit
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
|
@ -273,11 +426,74 @@ private fun Buttons(
|
|||
)
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
ButtonPrimary(
|
||||
onClick = { onDoneClicked(selectedIndex) },
|
||||
onClick = {
|
||||
onOpenClicked(progressState.wrapperObjId)
|
||||
},
|
||||
size = ButtonSize.Large,
|
||||
text = stringResource(id = R.string.done),
|
||||
text = stringResource(id = R.string.sharing_menu_btn_open),
|
||||
modifier = Modifier.weight(1.0f),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ButtonsProgress(
|
||||
onCancelProcessClicked: (Id) -> Unit,
|
||||
progressState: AddToAnytypeViewModel.ProgressState.Progress
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(68.dp)
|
||||
.padding(horizontal = 20.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
ButtonSecondary(
|
||||
onClick = { onCancelProcessClicked(progressState.processId) },
|
||||
size = ButtonSize.Large,
|
||||
text = stringResource(id = R.string.cancel),
|
||||
modifier = Modifier.weight(1.0f)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
ButtonPrimaryLoading(
|
||||
size = ButtonSize.Large,
|
||||
text = stringResource(id = R.string.sharing_menu_btn_add),
|
||||
modifierBox = Modifier.weight(1.0f),
|
||||
modifierButton = Modifier.fillMaxWidth(),
|
||||
loading = true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Buttons(
|
||||
onCancelClicked: () -> Unit,
|
||||
onAddClicked: (SaveAsOption) -> Unit,
|
||||
selectedIndex: Int,
|
||||
progressState: AddToAnytypeViewModel.ProgressState
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(68.dp)
|
||||
.padding(horizontal = 20.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
ButtonSecondary(
|
||||
onClick = onCancelClicked,
|
||||
size = ButtonSize.Large,
|
||||
text = stringResource(id = R.string.cancel),
|
||||
modifier = Modifier.weight(1.0f)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
ButtonPrimaryLoading(
|
||||
onClick = { onAddClicked(selectedIndex) },
|
||||
size = ButtonSize.Large,
|
||||
text = stringResource(id = R.string.sharing_menu_btn_add),
|
||||
modifierBox = Modifier.weight(1.0f),
|
||||
modifierButton = Modifier.fillMaxWidth(),
|
||||
loading = progressState is AddToAnytypeViewModel.ProgressState.Progress
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -292,10 +508,10 @@ private fun CurrentSpaceSection(
|
|||
val throttler = remember {
|
||||
MultipleEventCutter.Companion.get(interval = DROPDOWN_MENU_VISIBILITY_WINDOW_INTERVAL)
|
||||
}
|
||||
Box(
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(76.dp)
|
||||
.wrapContentHeight()
|
||||
.noRippleClickable {
|
||||
throttler.processEvent {
|
||||
isSpaceSelectMenuExpanded = true
|
||||
|
@ -309,29 +525,26 @@ private fun CurrentSpaceSection(
|
|||
style = Caption1Medium,
|
||||
color = colorResource(id = R.color.text_secondary)
|
||||
)
|
||||
val hasIcon = icon is SpaceIconView.Gradient || icon is SpaceIconView.Image
|
||||
if (icon != null && hasIcon) {
|
||||
SmallSpaceIcon(
|
||||
icon = icon,
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
start = 20.dp,
|
||||
bottom = 17.dp
|
||||
)
|
||||
.align(Alignment.BottomStart)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 20.dp, end = 20.dp, top = 6.dp, bottom = 14.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
val hasIcon = icon is SpaceIconView.Gradient || icon is SpaceIconView.Image
|
||||
if (icon != null && hasIcon) {
|
||||
SmallSpaceIcon(
|
||||
icon = icon,
|
||||
modifier = Modifier.padding(end = 8.dp)
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = name,
|
||||
modifier = Modifier,
|
||||
style = BodyRegular,
|
||||
color = colorResource(id = R.color.text_primary)
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = name,
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomStart)
|
||||
.padding(
|
||||
bottom = 14.dp,
|
||||
start = if (hasIcon) 44.dp else 20.dp
|
||||
),
|
||||
style = BodyRegular,
|
||||
color = colorResource(id = R.color.text_primary)
|
||||
)
|
||||
DropdownMenu(
|
||||
expanded = isSpaceSelectMenuExpanded,
|
||||
onDismissRequest = {
|
||||
|
@ -365,6 +578,7 @@ private fun CurrentSpaceSection(
|
|||
}
|
||||
}
|
||||
}
|
||||
com.anytypeio.anytype.core_ui.foundation.Divider(paddingEnd = 20.dp, paddingStart = 20.dp)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
@ -378,7 +592,7 @@ private fun Header() {
|
|||
text = stringResource(R.string.sharing_menu_add_to_anytype_header_title),
|
||||
color = colorResource(id = R.color.text_primary),
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
style = TitleInter15
|
||||
style = Title2
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -389,7 +603,7 @@ private fun SmallSpaceIcon(
|
|||
icon: SpaceIconView,
|
||||
modifier: Modifier
|
||||
) {
|
||||
val size = 18.dp
|
||||
val size = 20.dp
|
||||
when (icon) {
|
||||
is SpaceIconView.Image -> {
|
||||
Image(
|
||||
|
@ -419,6 +633,7 @@ private fun SmallSpaceIcon(
|
|||
.background(gradient)
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
// Draw nothing.
|
||||
}
|
||||
|
@ -435,30 +650,33 @@ typealias SaveAsOption = Int
|
|||
|
||||
sealed class SharingData {
|
||||
abstract val data: String
|
||||
|
||||
data class Url(val url: String) : SharingData() {
|
||||
override val data: String
|
||||
get() = url
|
||||
}
|
||||
|
||||
data class Text(val raw: String) : SharingData() {
|
||||
override val data: String
|
||||
get() = raw
|
||||
}
|
||||
|
||||
data class Image(val uri: String) : SharingData() {
|
||||
override val data: String
|
||||
get() = uri
|
||||
}
|
||||
|
||||
data class Images(val uris: List<String>): SharingData() {
|
||||
data class Images(val uris: List<String>) : SharingData() {
|
||||
override val data: String
|
||||
get() = uris.toString()
|
||||
}
|
||||
|
||||
data class Files(val uris: List<String>): SharingData() {
|
||||
data class Files(val uris: List<String>) : SharingData() {
|
||||
override val data: String
|
||||
get() = uris.toString()
|
||||
}
|
||||
|
||||
data class File(val uri: String): SharingData() {
|
||||
data class File(val uri: String) : SharingData() {
|
||||
override val data: String
|
||||
get() = uri
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import androidx.navigation.fragment.findNavController
|
|||
import com.anytypeio.anytype.R
|
||||
import com.anytypeio.anytype.core_utils.ext.arg
|
||||
import com.anytypeio.anytype.core_utils.ext.argStringList
|
||||
import com.anytypeio.anytype.core_utils.ext.getFormattedDateTime
|
||||
import com.anytypeio.anytype.core_utils.ext.toast
|
||||
import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetComposeFragment
|
||||
import com.anytypeio.anytype.di.common.componentManager
|
||||
|
@ -24,6 +25,7 @@ import com.anytypeio.anytype.presentation.home.OpenObjectNavigation
|
|||
import com.anytypeio.anytype.presentation.sharing.AddToAnytypeViewModel
|
||||
import com.anytypeio.anytype.ui.editor.EditorFragment
|
||||
import com.anytypeio.anytype.ui.settings.typography
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
|
@ -79,24 +81,29 @@ class SharingFragment : BaseBottomSheetComposeFragment() {
|
|||
}
|
||||
}.collectAsState(initial = "").value,
|
||||
data = sharedData,
|
||||
onDoneClicked = { option ->
|
||||
onAddClicked = { option ->
|
||||
when(option) {
|
||||
SAVE_AS_BOOKMARK -> vm.onCreateBookmark(url = sharedData.data)
|
||||
SAVE_AS_NOTE -> vm.onCreateNote(sharedData.data)
|
||||
SAVE_AS_IMAGE -> vm.onShareMedia(listOf(sharedData.data))
|
||||
SAVE_AS_FILE -> vm.onShareMedia(listOf(sharedData.data))
|
||||
SAVE_AS_IMAGES -> {
|
||||
SAVE_AS_IMAGES, SAVE_AS_IMAGE -> {
|
||||
val formattedDateTime = getFormattedDateTime(Locale.getDefault())
|
||||
val objTitle =
|
||||
getString(R.string.sharing_media_wrapper_object_title, formattedDateTime)
|
||||
val data = sharedData
|
||||
if (data is SharingData.Images) {
|
||||
vm.onShareMedia(uris = data.uris)
|
||||
vm.onShareMedia(uris = data.uris, wrapperObjTitle = objTitle)
|
||||
} else {
|
||||
toast("Unexpected data format")
|
||||
}
|
||||
}
|
||||
SAVE_AS_FILES -> {
|
||||
val formattedDateTime = getFormattedDateTime(Locale.getDefault())
|
||||
val objTitle =
|
||||
getString(R.string.sharing_files_wrapper_object_title, formattedDateTime)
|
||||
val data = sharedData
|
||||
if (data is SharingData.Files) {
|
||||
vm.onShareMedia(uris = data.uris)
|
||||
vm.onShareMedia(uris = data.uris, wrapperObjTitle = objTitle)
|
||||
} else {
|
||||
toast("Unexpected data format")
|
||||
}
|
||||
|
@ -109,7 +116,10 @@ class SharingFragment : BaseBottomSheetComposeFragment() {
|
|||
}
|
||||
},
|
||||
spaces = vm.spaceViews.collectAsStateWithLifecycle().value,
|
||||
onSelectSpaceClicked = { vm.onSelectSpaceClicked(it) }
|
||||
onSelectSpaceClicked = { vm.onSelectSpaceClicked(it) },
|
||||
progressState = vm.progressState.collectAsStateWithLifecycle().value,
|
||||
onOpenClicked = vm::proceedWithNavigation,
|
||||
onCancelProcessClicked = { processId -> }
|
||||
)
|
||||
LaunchedEffect(Unit) {
|
||||
vm.navigation.collect { nav ->
|
||||
|
|
|
@ -30,6 +30,14 @@ sealed class Command {
|
|||
val type: Block.Content.File.Type?
|
||||
)
|
||||
|
||||
class FileDrop(
|
||||
val space: SpaceId,
|
||||
val ctx: Id,
|
||||
val dropTarget: Id,
|
||||
val blockPosition: Position,
|
||||
val localFilePaths: List<String>
|
||||
)
|
||||
|
||||
class DownloadFile(
|
||||
val path: String,
|
||||
val objectId: Id
|
||||
|
|
|
@ -50,6 +50,7 @@ import com.anytypeio.anytype.presentation.util.downloader.UriFileProvider
|
|||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import java.io.File
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import timber.log.Timber
|
||||
|
@ -477,4 +478,31 @@ fun BaseBottomSheetComposeFragment.setupBottomSheetBehavior(paddingTop: Int) {
|
|||
state = BottomSheetBehavior.STATE_EXPANDED
|
||||
skipCollapsed = true
|
||||
}
|
||||
}
|
||||
|
||||
fun getLocalizedDateTimePattern(locale: Locale): String {
|
||||
// Get DateFormat instances for both date and time
|
||||
val dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, locale)
|
||||
val timeFormat = DateFormat.getTimeInstance(DateFormat.SHORT, locale)
|
||||
|
||||
// Convert to SimpleDateFormat to extract the pattern
|
||||
val datePattern = (dateFormat as? SimpleDateFormat)?.toPattern()
|
||||
val timePattern = (timeFormat as? SimpleDateFormat)?.toPattern()
|
||||
|
||||
// Combine date and time patterns
|
||||
return "$datePattern $timePattern"
|
||||
}
|
||||
|
||||
fun getFormattedDateTime(locale: Locale): String {
|
||||
// Get the current date and time
|
||||
val currentDate = Date()
|
||||
|
||||
// Get the localized pattern
|
||||
val localizedPattern = getLocalizedDateTimePattern(locale)
|
||||
|
||||
// Create a formatter with the localized pattern
|
||||
val simpleDateFormat = SimpleDateFormat(localizedPattern, locale)
|
||||
|
||||
// Format the current date and time
|
||||
return simpleDateFormat.format(currentDate)
|
||||
}
|
|
@ -265,6 +265,10 @@ class BlockDataRepository(
|
|||
command: Command.UploadFile
|
||||
): ObjectWrapper.File = remote.uploadFile(command)
|
||||
|
||||
override suspend fun fileDrop(command: Command.FileDrop): Payload {
|
||||
return remote.fileDrop(command)
|
||||
}
|
||||
|
||||
override suspend fun downloadFile(
|
||||
command: Command.DownloadFile
|
||||
): String = remote.downloadFile(command)
|
||||
|
|
|
@ -89,6 +89,7 @@ interface BlockRemote {
|
|||
suspend fun copy(command: Command.Copy): Response.Clipboard.Copy
|
||||
|
||||
suspend fun uploadFile(command: Command.UploadFile): ObjectWrapper.File
|
||||
suspend fun fileDrop(command: Command.FileDrop): Payload
|
||||
suspend fun downloadFile(command: Command.DownloadFile): String
|
||||
|
||||
suspend fun setRelationKey(command: Command.SetRelationKey): Payload
|
||||
|
|
|
@ -41,6 +41,7 @@ import com.anytypeio.anytype.domain.page.Undo
|
|||
interface BlockRepository {
|
||||
|
||||
suspend fun uploadFile(command: Command.UploadFile): ObjectWrapper.File
|
||||
suspend fun fileDrop(command: Command.FileDrop): Payload
|
||||
suspend fun downloadFile(command: Command.DownloadFile): String
|
||||
|
||||
suspend fun move(command: Command.Move): Payload
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
package com.anytypeio.anytype.domain.media
|
||||
|
||||
import com.anytypeio.anytype.core_models.Command
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.NO_VALUE
|
||||
import com.anytypeio.anytype.core_models.Payload
|
||||
import com.anytypeio.anytype.core_models.Position
|
||||
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 FileDrop @Inject constructor(
|
||||
private val repo: BlockRepository,
|
||||
dispatchers: AppCoroutineDispatchers
|
||||
) : ResultInteractor<FileDrop.Params, Payload>(dispatchers.io) {
|
||||
|
||||
override suspend fun doWork(params: Params): Payload = repo.fileDrop(
|
||||
command = Command.FileDrop(
|
||||
ctx = params.ctx,
|
||||
space = params.space,
|
||||
dropTarget = params.dropTarget,
|
||||
blockPosition = params.blockPosition,
|
||||
localFilePaths = params.localFilePaths
|
||||
)
|
||||
)
|
||||
|
||||
data class Params(
|
||||
val space: SpaceId,
|
||||
val ctx: Id,
|
||||
val dropTarget: Id = NO_VALUE,
|
||||
val blockPosition: Position = Position.NONE,
|
||||
val localFilePaths: List<String>
|
||||
)
|
||||
}
|
|
@ -1212,7 +1212,13 @@
|
|||
<string name="sharing_menu_save_as_files_option">Files</string>
|
||||
<string name="sharing_menu_data">Data</string>
|
||||
<string name="sharing_menu_add_to_anytype_header_title">Add to Anytype</string>
|
||||
<string name="sharing_menu_add_to_anytype_error">Anytype file upload error: %1$s</string>
|
||||
<string name="sharing_menu_add_to_anytype_success">All files have been successfully uploaded</string>
|
||||
<string name="sharing_menu_toast_object_added">New object is added to the space \'%1$s\'</string>
|
||||
<string name="sharing_media_wrapper_object_title">Shared Media, %1$s, this object is auxiliary and can be deleted; all media or files will remain in Space.</string>
|
||||
<string name="sharing_files_wrapper_object_title">Shared Files, %1$s, this object is auxiliary and can be deleted; all media or files will remain in Space.</string>
|
||||
<string name="sharing_menu_btn_add">Add</string>
|
||||
<string name="sharing_menu_btn_open">Open</string>
|
||||
|
||||
<!--endregion-->
|
||||
|
||||
|
|
|
@ -230,6 +230,10 @@ class BlockMiddleware(
|
|||
command: Command.UploadFile
|
||||
): ObjectWrapper.File = middleware.fileUpload(command)
|
||||
|
||||
override suspend fun fileDrop(command: Command.FileDrop): Payload {
|
||||
return middleware.fileDrop(command)
|
||||
}
|
||||
|
||||
override suspend fun downloadFile(
|
||||
command: Command.DownloadFile
|
||||
): String = middleware.fileDownload(command).localPath
|
||||
|
|
|
@ -813,7 +813,7 @@ class Middleware @Inject constructor(
|
|||
val request = Rpc.File.Upload.Request(
|
||||
localPath = command.path,
|
||||
type = type,
|
||||
spaceId = command.space?.id.orEmpty()
|
||||
spaceId = command.space.id
|
||||
)
|
||||
if (BuildConfig.DEBUG) logRequest(request)
|
||||
val response = service.fileUpload(request)
|
||||
|
@ -821,6 +821,20 @@ class Middleware @Inject constructor(
|
|||
return ObjectWrapper.File(response.details.orEmpty())
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun fileDrop(command: Command.FileDrop): Payload {
|
||||
val request = Rpc.File.Drop.Request(
|
||||
contextId = command.ctx,
|
||||
dropTargetId = command.dropTarget,
|
||||
position = command.blockPosition.toMiddlewareModel(),
|
||||
localFilePaths = command.localFilePaths
|
||||
)
|
||||
if (BuildConfig.DEBUG) logRequest(request)
|
||||
val response = service.fileDrop(request)
|
||||
if (BuildConfig.DEBUG) logResponse(response)
|
||||
return response.event.toPayload()
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun fileDownload(command: Command.DownloadFile): Rpc.File.Download.Response {
|
||||
val request = Rpc.File.Download.Request(
|
||||
|
|
|
@ -200,6 +200,9 @@ interface MiddlewareService {
|
|||
@Throws(Exception::class)
|
||||
fun spaceUsage(request: Rpc.File.SpaceUsage.Request): Rpc.File.SpaceUsage.Response
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun fileDrop(request: Rpc.File.Drop.Request): Rpc.File.Drop.Response
|
||||
|
||||
//endregion
|
||||
|
||||
//region UNSPLASH commands
|
||||
|
|
|
@ -633,6 +633,17 @@ class MiddlewareServiceImplementation @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun fileDrop(request: Rpc.File.Drop.Request): Rpc.File.Drop.Response {
|
||||
val encoded = Service.fileDrop(Rpc.File.Drop.Request.ADAPTER.encode(request))
|
||||
val response = Rpc.File.Drop.Response.ADAPTER.decode(encoded)
|
||||
val error = response.error
|
||||
if (error != null && error.code != Rpc.File.Drop.Response.Error.Code.NULL) {
|
||||
throw Exception(error.description)
|
||||
} else {
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
override fun fileDownload(request: Rpc.File.Download.Request): Rpc.File.Download.Response {
|
||||
val encoded = Service.fileDownload(Rpc.File.Download.Request.ADAPTER.encode(request))
|
||||
val response = Rpc.File.Download.Response.ADAPTER.decode(encoded)
|
||||
|
|
|
@ -11,7 +11,6 @@ import com.anytypeio.anytype.analytics.base.EventsDictionary.CLICK_ONBOARDING_TO
|
|||
import com.anytypeio.anytype.analytics.base.EventsPropertiesKey
|
||||
import com.anytypeio.anytype.analytics.event.EventAnalytics
|
||||
import com.anytypeio.anytype.analytics.props.Props
|
||||
import com.anytypeio.anytype.core_models.Block
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.MarketplaceObjectTypeIds
|
||||
import com.anytypeio.anytype.core_models.NO_VALUE
|
||||
|
@ -20,16 +19,20 @@ import com.anytypeio.anytype.core_models.ObjectWrapper
|
|||
import com.anytypeio.anytype.core_models.Relations
|
||||
import com.anytypeio.anytype.core_models.ext.EMPTY_STRING_VALUE
|
||||
import com.anytypeio.anytype.core_models.primitives.SpaceId
|
||||
import com.anytypeio.anytype.core_models.Process.Event
|
||||
import com.anytypeio.anytype.core_models.Process.State
|
||||
import com.anytypeio.anytype.core_utils.ext.msg
|
||||
import com.anytypeio.anytype.domain.account.AwaitAccountStartManager
|
||||
import com.anytypeio.anytype.domain.base.fold
|
||||
import com.anytypeio.anytype.domain.device.FileSharer
|
||||
import com.anytypeio.anytype.domain.media.FileDrop
|
||||
import com.anytypeio.anytype.domain.media.UploadFile
|
||||
import com.anytypeio.anytype.domain.misc.UrlBuilder
|
||||
import com.anytypeio.anytype.domain.multiplayer.Permissions
|
||||
import com.anytypeio.anytype.domain.objects.CreateBookmarkObject
|
||||
import com.anytypeio.anytype.domain.objects.CreatePrefilledNote
|
||||
import com.anytypeio.anytype.domain.spaces.GetSpaceViews
|
||||
import com.anytypeio.anytype.domain.workspace.EventProcessDropFilesChannel
|
||||
import com.anytypeio.anytype.domain.workspace.SpaceManager
|
||||
import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate
|
||||
import com.anytypeio.anytype.presentation.common.BaseViewModel
|
||||
|
@ -40,6 +43,8 @@ import com.anytypeio.anytype.presentation.spaces.SpaceIconView
|
|||
import com.anytypeio.anytype.presentation.spaces.spaceIcon
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
@ -58,10 +63,11 @@ class AddToAnytypeViewModel(
|
|||
private val urlBuilder: UrlBuilder,
|
||||
private val awaitAccountStartManager: AwaitAccountStartManager,
|
||||
private val analytics: Analytics,
|
||||
private val uploadFile: UploadFile,
|
||||
private val fileSharer: FileSharer,
|
||||
private val permissions: Permissions,
|
||||
private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate
|
||||
private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate,
|
||||
private val fileDrop: FileDrop,
|
||||
private val eventProcessChannel: EventProcessDropFilesChannel
|
||||
) : BaseViewModel(), AnalyticSpaceHelperDelegate by analyticSpaceHelperDelegate {
|
||||
|
||||
private val selectedSpaceId = MutableStateFlow(NO_VALUE)
|
||||
|
@ -71,9 +77,12 @@ class AddToAnytypeViewModel(
|
|||
val navigation = MutableSharedFlow<OpenObjectNavigation>()
|
||||
val spaceViews = MutableStateFlow<List<SpaceView>>(emptyList())
|
||||
val commands = MutableSharedFlow<Command>()
|
||||
val progressState = MutableStateFlow<ProgressState>(ProgressState.Init)
|
||||
|
||||
val state = MutableStateFlow<ViewState>(ViewState.Init)
|
||||
|
||||
private var progressJob: Job? = null
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
analytics.registerEvent(
|
||||
|
@ -136,7 +145,70 @@ class AddToAnytypeViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
fun onShareMedia(uris: List<String>) {
|
||||
private fun subscribeToEventProcessChannel(wrapperObjId: Id) {
|
||||
if (progressJob?.isActive == true) {
|
||||
progressJob?.cancel()
|
||||
}
|
||||
progressJob = viewModelScope.launch {
|
||||
eventProcessChannel.observe()
|
||||
.collect { events ->
|
||||
events.forEach { event ->
|
||||
when (event) {
|
||||
is Event.DropFiles.New -> {
|
||||
val currentProgressState = progressState.value
|
||||
if (currentProgressState is ProgressState.Init
|
||||
&& event.process.state == State.RUNNING
|
||||
) {
|
||||
progressState.value = ProgressState.Progress(
|
||||
processId = event.process.id,
|
||||
progress = 0f,
|
||||
wrapperObjId = wrapperObjId
|
||||
)
|
||||
} else {
|
||||
//some process is already running
|
||||
}
|
||||
}
|
||||
|
||||
is Event.DropFiles.Update -> {
|
||||
val currentProgressState = progressState.value
|
||||
val newProcess = event.process
|
||||
if (currentProgressState is ProgressState.Progress
|
||||
&& currentProgressState.processId == event.process.id
|
||||
&& newProcess.state == State.RUNNING
|
||||
) {
|
||||
val progress = newProcess.progress
|
||||
val total = progress?.total
|
||||
val done = progress?.done
|
||||
progressState.value =
|
||||
if (total != null && total != 0L && done != null) {
|
||||
currentProgressState.copy(progress = done.toFloat() / total)
|
||||
} else {
|
||||
currentProgressState.copy(progress = 0f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is Event.DropFiles.Done -> {
|
||||
val currentProgressState = progressState.value
|
||||
val newProcess = event.process
|
||||
if (currentProgressState is ProgressState.Progress
|
||||
&& event.process.state == State.DONE
|
||||
&& newProcess.id == currentProgressState.processId
|
||||
) {
|
||||
progressState.value = currentProgressState.copy(progress = 1f)
|
||||
delay(300)
|
||||
progressState.value = ProgressState.Done(
|
||||
wrapperObjId = currentProgressState.wrapperObjId
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onShareMedia(uris: List<String>, wrapperObjTitle: String? = null) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val targetSpaceView = spaceViews.value.firstOrNull { view ->
|
||||
view.isSelected
|
||||
|
@ -147,89 +219,98 @@ class AddToAnytypeViewModel(
|
|||
val paths = uris.mapNotNull { uri ->
|
||||
fileSharer.getPath(uri)
|
||||
}
|
||||
val files = mutableListOf<Id>()
|
||||
paths.forEach { path ->
|
||||
uploadFile.async(
|
||||
UploadFile.Params(
|
||||
path = path,
|
||||
space = SpaceId(targetSpaceId),
|
||||
// Temporary workaround to fix issue on the MW side.
|
||||
type = Block.Content.File.Type.NONE
|
||||
)
|
||||
).fold(
|
||||
onSuccess = { obj ->
|
||||
files.add(obj.id)
|
||||
},
|
||||
onFailure = { e ->
|
||||
Timber.e(e, "Error while uploading file").also {
|
||||
sendToast(e.msg())
|
||||
}
|
||||
}
|
||||
|
||||
when (paths.size) {
|
||||
0 -> sendToast("Could not get file paths")
|
||||
else -> proceedWithCreatingWrapperObject(
|
||||
filePaths = paths,
|
||||
targetSpaceId = targetSpaceId,
|
||||
wrapperObjTitle = wrapperObjTitle,
|
||||
)
|
||||
}
|
||||
when (files.size) {
|
||||
0 -> {
|
||||
sendToast("Could not upload files")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1 -> {
|
||||
// No need to create a wrapper object, opening file object directly instead
|
||||
if (targetSpaceId == spaceManager.get()) {
|
||||
navigation.emit(
|
||||
OpenObjectNavigation.OpenEditor(
|
||||
target = files.first(),
|
||||
space = targetSpaceId
|
||||
)
|
||||
)
|
||||
} else {
|
||||
with(commands) {
|
||||
emit(Command.ObjectAddToSpaceToast(targetSpaceView.obj.name))
|
||||
emit(Command.Dismiss)
|
||||
}
|
||||
}
|
||||
}
|
||||
private suspend fun proceedWithCreatingWrapperObject(
|
||||
filePaths: List<String>,
|
||||
targetSpaceId: String,
|
||||
wrapperObjTitle: String? = null
|
||||
) {
|
||||
val startTime = System.currentTimeMillis()
|
||||
createPrefilledNote.async(
|
||||
CreatePrefilledNote.Params(
|
||||
text = wrapperObjTitle ?: EMPTY_STRING_VALUE,
|
||||
space = targetSpaceId,
|
||||
details = mapOf(
|
||||
Relations.ORIGIN to ObjectOrigin.SHARING_EXTENSION.code.toDouble(),
|
||||
),
|
||||
)
|
||||
).fold(
|
||||
onSuccess = { wrapperObjId ->
|
||||
viewModelScope.sendAnalyticsObjectCreateEvent(
|
||||
analytics = analytics,
|
||||
objType = MarketplaceObjectTypeIds.PAGE,
|
||||
route = EventsDictionary.Routes.sharingExtension,
|
||||
startTime = startTime,
|
||||
spaceParams = provideParams(spaceManager.get())
|
||||
)
|
||||
proceedWithFilesDrop(
|
||||
wrapperObjId = wrapperObjId,
|
||||
filePaths = filePaths,
|
||||
targetSpaceId = targetSpaceId
|
||||
)
|
||||
},
|
||||
onFailure = {
|
||||
Timber.d(it, "Error while creating page")
|
||||
sendToast("Error while creating page: ${it.msg()}")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
// Creating a wrapper object for file objects
|
||||
val startTime = System.currentTimeMillis()
|
||||
createPrefilledNote.async(
|
||||
CreatePrefilledNote.Params(
|
||||
text = EMPTY_STRING_VALUE,
|
||||
space = targetSpaceId,
|
||||
details = mapOf(
|
||||
Relations.ORIGIN to ObjectOrigin.SHARING_EXTENSION.code.toDouble()
|
||||
),
|
||||
attachments = files
|
||||
)
|
||||
).fold(
|
||||
onSuccess = { result ->
|
||||
sendAnalyticsObjectCreateEvent(
|
||||
analytics = analytics,
|
||||
objType = MarketplaceObjectTypeIds.NOTE,
|
||||
route = EventsDictionary.Routes.sharingExtension,
|
||||
startTime = startTime,
|
||||
spaceParams = provideParams(spaceManager.get())
|
||||
)
|
||||
if (targetSpaceId == spaceManager.get()) {
|
||||
navigation.emit(
|
||||
OpenObjectNavigation.OpenEditor(
|
||||
target = result,
|
||||
space = targetSpaceId
|
||||
)
|
||||
)
|
||||
} else {
|
||||
with(commands) {
|
||||
emit(Command.ObjectAddToSpaceToast(targetSpaceView.obj.name))
|
||||
emit(Command.Dismiss)
|
||||
}
|
||||
}
|
||||
},
|
||||
onFailure = {
|
||||
Timber.d(it, "Error while creating note")
|
||||
sendToast("Error while creating note: ${it.msg()}")
|
||||
}
|
||||
)
|
||||
}
|
||||
private suspend fun proceedWithFilesDrop(
|
||||
wrapperObjId: Id,
|
||||
filePaths: List<String>,
|
||||
targetSpaceId: String,
|
||||
) {
|
||||
subscribeToEventProcessChannel(wrapperObjId = wrapperObjId)
|
||||
val params = FileDrop.Params(
|
||||
ctx = wrapperObjId,
|
||||
space = SpaceId(targetSpaceId),
|
||||
localFilePaths = filePaths
|
||||
)
|
||||
fileDrop.async(params).fold(
|
||||
onSuccess = { _ -> Timber.d("Files dropped successfully") },
|
||||
onFailure = { e ->
|
||||
Timber.e(e, "Error while dropping files").also {
|
||||
sendToast(e.msg())
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun proceedWithNavigation(wrapperObjId: Id) {
|
||||
val targetSpaceView = spaceViews.value.firstOrNull { view ->
|
||||
view.isSelected
|
||||
}
|
||||
val targetSpaceId = targetSpaceView?.obj?.targetSpaceId
|
||||
viewModelScope.launch {
|
||||
Timber.d("proceedWithNavigation: $wrapperObjId, $targetSpaceId")
|
||||
if (targetSpaceId == spaceManager.get()) {
|
||||
Timber.d("proceedWithNavigation: OpenEditor")
|
||||
delay(300)
|
||||
navigation.emit(
|
||||
OpenObjectNavigation.OpenEditor(
|
||||
target = wrapperObjId,
|
||||
space = targetSpaceId
|
||||
)
|
||||
)
|
||||
} else {
|
||||
Timber.d("proceedWithNavigation: ObjectAddToSpaceToast")
|
||||
delay(300)
|
||||
with(commands) {
|
||||
emit(Command.ObjectAddToSpaceToast(targetSpaceView?.obj?.name))
|
||||
emit(Command.Dismiss)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -381,10 +462,11 @@ class AddToAnytypeViewModel(
|
|||
private val urlBuilder: UrlBuilder,
|
||||
private val awaitAccountStartManager: AwaitAccountStartManager,
|
||||
private val analytics: Analytics,
|
||||
private val uploadFile: UploadFile,
|
||||
private val fileSharer: FileSharer,
|
||||
private val permissions: Permissions,
|
||||
private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate
|
||||
private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate,
|
||||
private val fileDrop: FileDrop,
|
||||
private val eventProcessChannel: EventProcessDropFilesChannel
|
||||
) : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
|
@ -396,10 +478,11 @@ class AddToAnytypeViewModel(
|
|||
urlBuilder = urlBuilder,
|
||||
awaitAccountStartManager = awaitAccountStartManager,
|
||||
analytics = analytics,
|
||||
uploadFile = uploadFile,
|
||||
fileSharer = fileSharer,
|
||||
permissions = permissions,
|
||||
analyticSpaceHelperDelegate = analyticSpaceHelperDelegate
|
||||
analyticSpaceHelperDelegate = analyticSpaceHelperDelegate,
|
||||
fileDrop = fileDrop,
|
||||
eventProcessChannel = eventProcessChannel
|
||||
) as T
|
||||
}
|
||||
}
|
||||
|
@ -411,17 +494,26 @@ class AddToAnytypeViewModel(
|
|||
)
|
||||
|
||||
sealed class Command {
|
||||
object Dismiss : Command()
|
||||
data object Dismiss : Command()
|
||||
data class ObjectAddToSpaceToast(
|
||||
val spaceName: String?
|
||||
) : Command()
|
||||
}
|
||||
|
||||
sealed class ViewState {
|
||||
object Init : ViewState()
|
||||
data object Init : ViewState()
|
||||
data class Default(val content: String) : ViewState()
|
||||
}
|
||||
|
||||
sealed class ProgressState {
|
||||
data object Init : ProgressState()
|
||||
data class Progress(val wrapperObjId: Id, val processId: Id, val progress: Float) :
|
||||
ProgressState()
|
||||
|
||||
data class Done(val wrapperObjId: Id) : ProgressState()
|
||||
data class Error(val error: String) : ProgressState()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val FILE_NAME_SEPARATOR = ", "
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue