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

DROID-2195 Multispaces | Tech | Always provide space id when uploading file (#872)

This commit is contained in:
Evgenii Kozlov 2024-02-15 16:04:58 +01:00 committed by GitHub
parent e633a3e136
commit cd3a77b5d0
Signed by: github
GPG key ID: B5690EEEBB952194
30 changed files with 339 additions and 107 deletions

View file

@ -167,7 +167,6 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.consumeAsFlow
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach
@ -316,7 +315,7 @@ open class EditorFragment : NavigationFragment<FragmentEditorBinding>(R.layout.f
)
},
onTextInputClicked = vm::onTextInputClicked,
onPageIconClicked = vm::onPageIconClicked,
onPageIconClicked = vm::onObjectIconClicked,
onCoverClicked = vm::onAddCoverClicked,
onTogglePlaceholderClicked = vm::onTogglePlaceholderClicked,
onToggleClicked = vm::onToggleClicked,
@ -895,13 +894,14 @@ open class EditorFragment : NavigationFragment<FragmentEditorBinding>(R.layout.f
context = ctx, blockId = command.block
).showChildFragment()
}
Command.OpenDocumentEmojiIconPicker -> {
is Command.OpenDocumentEmojiIconPicker -> {
hideSoftInput()
findNavController().safeNavigate(
R.id.pageScreen,
R.id.action_pageScreen_to_objectIconPickerScreen,
bundleOf(
IconPickerFragmentBase.ARG_CONTEXT_ID_KEY to ctx,
IconPickerFragmentBase.ARG_CONTEXT_ID_KEY to command.ctx,
IconPickerFragmentBase.ARG_SPACE_ID_KEY to command.space,
)
)
}
@ -937,7 +937,8 @@ open class EditorFragment : NavigationFragment<FragmentEditorBinding>(R.layout.f
is Command.OpenDocumentMenu -> {
hideKeyboard()
val fr = ObjectMenuFragment.new(
ctx = ctx,
ctx = command.ctx,
space = command.space,
isArchived = command.isArchived,
isFavorite = command.isFavorite,
isLocked = command.isLocked,
@ -1180,6 +1181,16 @@ open class EditorFragment : NavigationFragment<FragmentEditorBinding>(R.layout.f
)
)
}
is Command.SetObjectIcon -> {
findNavController().safeNavigate(
R.id.pageScreen,
R.id.objectIconPickerScreen,
bundleOf(
IconPickerFragmentBase.ARG_CONTEXT_ID_KEY to ctx,
IconPickerFragmentBase.ARG_SPACE_ID_KEY to command.space
)
)
}
}
}
}
@ -2008,13 +2019,7 @@ open class EditorFragment : NavigationFragment<FragmentEditorBinding>(R.layout.f
}
override fun onSetIconClicked() {
findNavController().safeNavigate(
R.id.pageScreen,
R.id.objectIconPickerScreen,
bundleOf(
IconPickerFragmentBase.ARG_CONTEXT_ID_KEY to ctx,
)
)
vm.onSetObjectIconClicked()
}
override fun onLayoutClicked() {

View file

@ -35,6 +35,9 @@ abstract class IconPickerFragmentBase<T> :
protected val context: Id
get() = arg(ARG_CONTEXT_ID_KEY)
protected val space: Id
get() = arg(ARG_SPACE_ID_KEY)
/**
* The target for which we choose icon
* i.e. Object, callout text block
@ -145,7 +148,8 @@ abstract class IconPickerFragmentBase<T> :
val path = uri.parseImagePath(requireContext())
vm.onPickedFromDevice(
iconable = target,
path = path
path = path,
space = space
)
} catch (e: Exception) {
toast("Error while parsing path for cover image")
@ -175,6 +179,7 @@ abstract class IconPickerFragmentBase<T> :
private const val UNEXPECTED_VIEW_TYPE_MESSAGE = "Unexpected view type"
const val ARG_CONTEXT_ID_KEY = "arg.picker.context.id"
const val ARG_SPACE_ID_KEY = "arg.picker.space.id"
private const val SELECT_IMAGE_CODE = 1
}

View file

@ -4,8 +4,8 @@ import androidx.core.os.bundleOf
import androidx.fragment.app.viewModels
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.di.common.componentManager
import com.anytypeio.anytype.presentation.picker.IconPickerViewModel
import com.anytypeio.anytype.presentation.editor.picker.ObjectIconPickerViewModelFactory
import com.anytypeio.anytype.presentation.picker.IconPickerViewModel
import javax.inject.Inject
open class ObjectIconPickerFragment : IconPickerFragmentBase<Id>() {
@ -26,9 +26,10 @@ open class ObjectIconPickerFragment : IconPickerFragmentBase<Id>() {
}
companion object {
fun new(context: String) = ObjectIconPickerFragment().apply {
fun new(ctx: Id, space: Id) = ObjectIconPickerFragment().apply {
arguments = bundleOf(
ARG_CONTEXT_ID_KEY to context,
ARG_CONTEXT_ID_KEY to ctx,
ARG_SPACE_ID_KEY to space
)
}
}

View file

@ -44,6 +44,7 @@ abstract class ObjectMenuBaseFragment :
BacklinkAction {
protected val ctx get() = arg<Id>(CTX_KEY)
protected val space get() = arg<Id>(SPACE_KEY)
private val isArchived get() = arg<Boolean>(IS_ARCHIVED_KEY)
private val isFavorite get() = arg<Boolean>(IS_FAVORITE_KEY)
private val isLocked get() = arg<Boolean>(IS_LOCKED_KEY)
@ -224,6 +225,7 @@ abstract class ObjectMenuBaseFragment :
R.id.objectSetIconPickerScreen,
bundleOf(
IconPickerFragmentBase.ARG_CONTEXT_ID_KEY to ctx,
IconPickerFragmentBase.ARG_SPACE_ID_KEY to space
)
)
}
@ -287,6 +289,7 @@ abstract class ObjectMenuBaseFragment :
companion object {
const val CTX_KEY = "arg.doc-menu-bottom-sheet.ctx"
const val SPACE_KEY = "arg.doc-menu-bottom-sheet.space"
const val IS_ARCHIVED_KEY = "arg.doc-menu-bottom-sheet.is-archived"
const val IS_FAVORITE_KEY = "arg.doc-menu-bottom-sheet.is-favorite"
const val IS_LOCKED_KEY = "arg.doc-menu-bottom-sheet.is-locked"

View file

@ -39,6 +39,7 @@ class ObjectMenuFragment : ObjectMenuBaseFragment() {
fun new(
ctx: Id,
space: Id,
isArchived: Boolean,
isFavorite: Boolean,
isLocked: Boolean,
@ -47,6 +48,7 @@ class ObjectMenuFragment : ObjectMenuBaseFragment() {
) = ObjectMenuFragment().apply {
arguments = bundleOf(
CTX_KEY to ctx,
SPACE_KEY to space,
IS_ARCHIVED_KEY to isArchived,
IS_FAVORITE_KEY to isFavorite,
IS_LOCKED_KEY to isLocked,

View file

@ -718,7 +718,7 @@ open class ObjectSetFragment :
if (header.title.emoji != null) visible() else gone()
jobs += this.clicks()
.throttleFirst()
.onEach { vm.onIconClicked() }
.onEach { vm.onObjectIconClicked() }
.launchIn(lifecycleScope)
}
@ -726,7 +726,7 @@ open class ObjectSetFragment :
if (header.title.image != null) visible() else gone()
jobs += this.clicks()
.throttleFirst()
.onEach { vm.onIconClicked() }
.onEach { vm.onObjectIconClicked() }
.launchIn(lifecycleScope)
}
@ -894,6 +894,7 @@ open class ObjectSetFragment :
R.id.objectSetMainMenuScreen,
bundleOf(
ObjectMenuBaseFragment.CTX_KEY to command.ctx,
ObjectMenuBaseFragment.SPACE_KEY to command.space,
ObjectMenuBaseFragment.IS_ARCHIVED_KEY to command.isArchived,
ObjectMenuBaseFragment.IS_FAVORITE_KEY to command.isFavorite,
ObjectMenuBaseFragment.IS_LOCKED_KEY to false,
@ -1019,7 +1020,8 @@ open class ObjectSetFragment :
R.id.objectSetScreen,
R.id.action_objectSetScreen_to_objectSetIconPickerScreen,
bundleOf(
IconPickerFragmentBase.ARG_CONTEXT_ID_KEY to ctx,
IconPickerFragmentBase.ARG_CONTEXT_ID_KEY to command.target,
IconPickerFragmentBase.ARG_SPACE_ID_KEY to command.space,
)
)
}

View file

@ -15,10 +15,12 @@ import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import com.anytypeio.anytype.R
import com.anytypeio.anytype.analytics.base.EventsDictionary
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_ui.common.ComposeDialogView
import com.anytypeio.anytype.core_ui.extensions.throttledClick
import com.anytypeio.anytype.core_utils.ext.GetImageContract
import com.anytypeio.anytype.core_utils.ext.Mimetype
import com.anytypeio.anytype.core_utils.ext.arg
import com.anytypeio.anytype.core_utils.ext.parseImagePath
import com.anytypeio.anytype.core_utils.ext.setupBottomSheetBehavior
import com.anytypeio.anytype.core_utils.ext.shareFile
@ -37,6 +39,8 @@ import timber.log.Timber
class ProfileSettingsFragment : BaseBottomSheetComposeFragment() {
private val space: Id get() = arg(SPACE_ID_KEY)
@Inject
lateinit var factory: ProfileSettingsViewModel.Factory
@ -145,7 +149,7 @@ class ProfileSettingsFragment : BaseBottomSheetComposeFragment() {
if (uri != null) {
try {
val path = uri.parseImagePath(requireContext())
vm.onPickedImageFromDevice(path = path)
vm.onPickedImageFromDevice(path = path, space = space)
} catch (e: Exception) {
toast("Error while parsing path for cover image")
Timber.d(e, "Error while parsing path for cover image")
@ -162,6 +166,10 @@ class ProfileSettingsFragment : BaseBottomSheetComposeFragment() {
override fun releaseDependencies() {
componentManager().profileComponent.release()
}
companion object {
const val SPACE_ID_KEY = "arg.profile-settings.space-id"
}
}
private const val PADDING_TOP = 28

View file

@ -20,6 +20,7 @@ import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetComposeFragment
import com.anytypeio.anytype.di.common.componentManager
import com.anytypeio.anytype.presentation.spaces.Command
import com.anytypeio.anytype.presentation.spaces.SelectSpaceViewModel
import com.anytypeio.anytype.ui.settings.ProfileSettingsFragment
import com.anytypeio.anytype.ui.settings.typography
import javax.inject.Inject
import timber.log.Timber
@ -58,10 +59,10 @@ class SelectSpaceFragment : BaseBottomSheetComposeFragment() {
onClick = { vm.onCreateSpaceClicked() }
),
onSettingsClicked = throttledClick(
onClick = { findNavController().navigate(R.id.profileScreen) }
onClick = vm::onProfileSettingsClicked
),
onProfileClicked = throttledClick(
onClick = { findNavController().navigate(R.id.profileScreen) }
onClick = vm::onProfileSettingsClicked
)
)
}
@ -95,6 +96,16 @@ class SelectSpaceFragment : BaseBottomSheetComposeFragment() {
Timber.e(e, "Navigation error")
}
}
is Command.NavigateToProfileSettings -> {
try {
findNavController().navigate(
R.id.profileScreen,
bundleOf(ProfileSettingsFragment.SPACE_ID_KEY to command.space)
)
} catch (e: Exception) {
Timber.e(e, "Navigation error")
}
}
}
}

View file

@ -21,7 +21,7 @@ sealed class Command {
) : Command()
class UploadFile(
val space: SpaceId? = null,
val space: SpaceId,
val path: String,
val type: Block.Content.File.Type?
)

View file

@ -136,7 +136,10 @@ sealed class ObjectWrapper {
val notDeletedNorArchived get() = (isDeleted != true && isArchived != true)
val targetSpaceId: String? by default
val spaceId: Id? by default
// N.B. Only used for space view objects
val targetSpaceId: Id? by default
val backlinks get() = getValues<Id>(Relations.BACKLINKS)
}
@ -248,6 +251,8 @@ sealed class ObjectWrapper {
val name: String? by default
val iconImage: String? get() = getSingleValue(Relations.ICON_IMAGE)
val iconOption: Double? by default
// N.B. Only used for space view objects
val targetSpaceId: String? by default
val spaceAccountStatus: SpaceStatus

View file

@ -13,7 +13,8 @@ class SetDocumentImageIcon(
val file = repo.uploadFile(
command = Command.UploadFile(
path = params.path,
type = Block.Content.File.Type.IMAGE
type = Block.Content.File.Type.IMAGE,
space = params.spaceId
)
)
val payload = repo.setDocumentImageIcon(

View file

@ -2,6 +2,7 @@ package com.anytypeio.anytype.domain.icon
import com.anytypeio.anytype.core_models.Hash
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.domain.base.BaseUseCase
abstract class SetImageIcon<T> : BaseUseCase<Pair<Payload, Hash>, SetImageIcon.Params<T>>() {
@ -12,6 +13,7 @@ abstract class SetImageIcon<T> : BaseUseCase<Pair<Payload, Hash>, SetImageIcon.P
*/
data class Params<T>(
val target: T,
val path: String
val path: String,
val spaceId: SpaceId
)
}

View file

@ -14,7 +14,8 @@ class SetTextBlockImage(
val file = repo.uploadFile(
command = Command.UploadFile(
path = params.path,
type = Block.Content.File.Type.IMAGE
type = Block.Content.File.Type.IMAGE,
space = params.spaceId
)
)
val payload = repo.setTextIcon(

View file

@ -4,7 +4,6 @@ import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.ObjectView
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.domain.auth.repo.AuthRepository
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.base.ResultInteractor
import com.anytypeio.anytype.domain.block.repo.BlockRepository
@ -20,7 +19,7 @@ class OpenObject @Inject constructor(
override suspend fun doWork(params: Params) = repo.openObject(params.obj).also {
if (params.saveAsLastOpened) {
val obj = ObjectWrapper.Basic(it.details[params.obj].orEmpty())
val space = obj.targetSpaceId
val space = obj.spaceId
if (!space.isNullOrEmpty()) {
settings.setLastOpenedObject(
id = params.obj,

View file

@ -4,6 +4,7 @@ import com.anytypeio.anytype.core_models.Command
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_models.ext.addIds
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.domain.base.BaseUseCase
import com.anytypeio.anytype.domain.base.Either
import com.anytypeio.anytype.domain.block.repo.BlockRepository
@ -16,7 +17,8 @@ class AddFileToObject(
val file = repo.uploadFile(
command = Command.UploadFile(
path = params.path,
type = null
type = null,
space = params.space
)
)
val obj = params.obj
@ -32,6 +34,7 @@ class AddFileToObject(
val ctx: Id,
val relation: Id,
val obj: Map<String, Any?>,
val path: String
val path: String,
val space: SpaceId
)
}

View file

@ -8,6 +8,7 @@ import com.anytypeio.anytype.core_models.BlockSplitMode
import com.anytypeio.anytype.core_models.Command
import com.anytypeio.anytype.core_models.Position
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.core_models.primitives.TypeKey
import com.anytypeio.anytype.middleware.interactor.Middleware
import com.anytypeio.anytype.middleware.interactor.MiddlewareFactory
@ -512,12 +513,14 @@ class MiddlewareTest {
val command = Command.UploadFile(
path = path,
type = CBlockFileType.IMAGE
type = CBlockFileType.IMAGE,
space = SpaceId(MockDataFactory.randomUuid())
)
val request = Rpc.File.Upload.Request(
localPath = path,
type = Block.Content.File.Type.Image
type = Block.Content.File.Type.Image,
spaceId = command.space.id
)
service.stub {
@ -541,12 +544,14 @@ class MiddlewareTest {
val command = Command.UploadFile(
path = path,
type = CBlockFileType.FILE
type = CBlockFileType.FILE,
space = SpaceId(MockDataFactory.randomUuid())
)
val request = Rpc.File.Upload.Request(
localPath = path,
type = Block.Content.File.Type.File
type = Block.Content.File.Type.File,
spaceId = command.space.id
)
service.stub {
@ -570,12 +575,14 @@ class MiddlewareTest {
val command = Command.UploadFile(
path = path,
type = CBlockFileType.VIDEO
type = CBlockFileType.VIDEO,
space = SpaceId(MockDataFactory.randomUuid())
)
val request = Rpc.File.Upload.Request(
localPath = path,
type = Block.Content.File.Type.Video
type = Block.Content.File.Type.Video,
spaceId = command.space.id
)
service.stub {

View file

@ -460,20 +460,29 @@ class EditorViewModel(
override fun onPickedDocImageFromDevice(ctx: Id, path: String) {
viewModelScope.launch {
setDocImageIcon(
SetImageIcon.Params(
target = ctx,
path = path
val obj = orchestrator.stores.details.getAsObject(ctx)
val space = obj?.spaceId
if (space != null) {
setDocImageIcon(
SetImageIcon.Params(
target = ctx,
path = path,
spaceId = SpaceId(space)
)
).process(
failure = {
sendToast("Can't update object icon image")
Timber.e(it, "Error while setting image icon")
},
success = { (payload, _) ->
dispatcher.send(payload)
}
)
).process(
failure = {
sendToast("Can't update object icon image")
Timber.e(it, "Error while setting image icon")
},
success = { (payload, _) ->
dispatcher.send(payload)
} else {
Timber.e("Space not found").also {
sendToast("Space not found")
}
)
}
}
}
@ -1516,10 +1525,17 @@ class EditorViewModel(
val details = orchestrator.stores.details.current().details
val wrapper = ObjectWrapper.Basic(details[context]?.map.orEmpty())
val isTemplate = isObjectTemplate()
val space = wrapper.spaceId
if (space == null) {
sendToast("Space not found")
return
}
when {
isTemplate -> {
dispatch(
command = Command.OpenDocumentMenu(
ctx = context,
space = space,
isArchived = false,
isFavorite = false,
isLocked = false,
@ -1531,6 +1547,8 @@ class EditorViewModel(
else -> {
dispatch(
command = Command.OpenDocumentMenu(
ctx = context,
space = space,
isArchived = details[context]?.isArchived ?: false,
isFavorite = details[context]?.isFavorite ?: false,
isLocked = mode == EditorMode.Locked,
@ -3254,6 +3272,20 @@ class EditorViewModel(
}
}
fun onSetObjectIconClicked() {
viewModelScope.launch {
val obj = orchestrator.stores.details.getAsObject(context)
val space = obj?.spaceId
if (space != null) {
dispatch(Command.SetObjectIcon(ctx = context, space = space))
} else {
Timber.e("Space not found").also {
sendToast("Space not found")
}
}
}
}
fun onLayoutClicked() {
Timber.d("onLayoutClicked, ")
dispatch(Command.OpenObjectLayout(context))
@ -4137,7 +4169,7 @@ class EditorViewModel(
currentMediaUploadDescription = uploadMediaDescription
}
fun onPageIconClicked() {
fun onObjectIconClicked() {
Timber.d("onPageIconClicked, ")
if (mode == EditorMode.Locked) {
sendToast("Unlock your object to change its icon")
@ -4147,7 +4179,18 @@ class EditorViewModel(
val isDetailsAllowed = restrictions.none { it == ObjectRestriction.DETAILS }
if (isDetailsAllowed) {
controlPanelInteractor.onEvent(ControlPanelMachine.Event.OnDocumentIconClicked)
dispatch(Command.OpenDocumentEmojiIconPicker)
val obj = orchestrator.stores.details.getAsObject(context)
val space = obj?.spaceId
if (space != null) {
dispatch(
Command.OpenDocumentEmojiIconPicker(
ctx = context,
space = space
)
)
} else {
sendToast("Space not found")
}
} else {
sendToast(NOT_ALLOWED_FOR_OBJECT)
}

View file

@ -10,7 +10,10 @@ sealed class Command {
data class OpenDocumentImagePicker(val mimeType: Mimetype) : Command()
object OpenDocumentEmojiIconPicker : Command()
data class OpenDocumentEmojiIconPicker(
val ctx: Id,
val space: Id
) : Command()
data class OpenTextBlockIconPicker(val block: Id) : Command()
@ -54,6 +57,8 @@ sealed class Command {
) : Command()
data class OpenDocumentMenu(
val ctx: Id,
val space: Id,
val isArchived: Boolean,
val isFavorite: Boolean,
val isLocked: Boolean,
@ -61,8 +66,9 @@ sealed class Command {
val isTemplate: Boolean
) : Command()
data class OpenCoverGallery(val ctx: String) : Command()
data class OpenObjectLayout(val ctx: String) : Command()
data class OpenCoverGallery(val ctx: Id) : Command()
data class OpenObjectLayout(val ctx: Id) : Command()
data class SetObjectIcon(val ctx: Id, val space: Id) : Command()
object AlertDialog : Command()

View file

@ -2,6 +2,7 @@ package com.anytypeio.anytype.presentation.editor.editor
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.Relation
import com.anytypeio.anytype.core_models.RelationLink
import com.anytypeio.anytype.core_models.restrictions.ObjectRestriction
@ -60,6 +61,14 @@ interface Store<T> {
suspend fun add(target: Id, fields: Block.Fields) {
update(current().copy(details = current().details + mapOf(target to fields)))
}
fun getAsObject(target: Id) : ObjectWrapper.Basic? {
val struct = current().details[target]
return if (struct != null && struct.map.isNotEmpty()) {
ObjectWrapper.Basic(struct.map)
} else {
null
}
}
}
class Relations : State<List<Relation>>(emptyList())

View file

@ -4,7 +4,9 @@ import androidx.lifecycle.viewModelScope
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.analytics.base.EventsDictionary
import com.anytypeio.anytype.analytics.base.sendEvent
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.domain.icon.RemoveIcon
import com.anytypeio.anytype.domain.icon.SetEmojiIcon
import com.anytypeio.anytype.domain.icon.SetImageIcon
@ -62,13 +64,14 @@ class ObjectIconPickerViewModel<Iconable>(
}
}
override fun onPickedFromDevice(iconable: Iconable, path: String) {
override fun onPickedFromDevice(iconable: Iconable, path: String, space: Id) {
viewModelScope.launch {
state.value = ViewState.Loading
setImageIcon(
SetImageIcon.Params(
target = iconable,
path = path
path = path,
spaceId = SpaceId(space)
)
).process(
failure = {

View file

@ -2,6 +2,7 @@ package com.anytypeio.anytype.presentation.picker
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.emojifier.data.Emoji
import com.anytypeio.anytype.emojifier.data.EmojiProvider
import com.anytypeio.anytype.emojifier.suggest.EmojiSuggester
@ -41,7 +42,7 @@ abstract class IconPickerViewModel<Iconable>(
abstract fun setEmoji(iconable: Iconable, emojiUnicode: String)
abstract fun onRemoveClicked(iconable: Iconable)
abstract fun onPickedFromDevice(iconable: Iconable, path: String)
abstract fun onPickedFromDevice(iconable: Iconable, path: String, space: Id)
init {
viewModelScope.launch {

View file

@ -9,7 +9,6 @@ import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.Key
import com.anytypeio.anytype.core_models.Marketplace.MARKETPLACE_SPACE_ID
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.ObjectTypeIds.SET
import com.anytypeio.anytype.core_models.ObjectTypeUniqueKeys
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.core_models.primitives.TypeKey
@ -582,6 +581,7 @@ object ObjectSearchConstants {
val defaultKeys = listOf(
Relations.ID,
Relations.SPACE_ID,
Relations.TARGET_SPACE_ID,
Relations.UNIQUE_KEY,
Relations.NAME,
Relations.ICON_IMAGE,
@ -610,6 +610,8 @@ object ObjectSearchConstants {
val defaultDataViewKeys = listOf(
Relations.ID,
Relations.SPACE_ID,
Relations.TARGET_SPACE_ID,
Relations.IDENTITY_PROFILE_LINK,
Relations.NAME,
Relations.ICON_IMAGE,
@ -633,6 +635,7 @@ object ObjectSearchConstants {
val defaultRelationKeys = listOf(
Relations.ID,
Relations.SPACE_ID,
Relations.TARGET_SPACE_ID,
Relations.UNIQUE_KEY,
Relations.NAME,
Relations.DESCRIPTION,

View file

@ -9,6 +9,7 @@ sealed class ObjectSetCommand {
data class Menu(
val ctx: Id,
val space: Id,
val isArchived: Boolean,
val isFavorite: Boolean
) : Modal()
@ -70,7 +71,8 @@ sealed class ObjectSetCommand {
data class CreateBookmark(val ctx: Id) : Modal()
data class OpenIconActionMenu(
val target: Id
val target: Id,
val space: Id
) : Modal()
data class OpenCoverActionMenu(

View file

@ -1212,18 +1212,44 @@ class ObjectSetViewModel(
fun onMenuClicked() {
Timber.d("onMenuClicked, ")
val state = stateReducer.state.value.dataViewState() ?: return
dispatch(
ObjectSetCommand.Modal.Menu(
ctx = context,
isArchived = state.details[context]?.isArchived ?: false,
isFavorite = state.details[context]?.isFavorite ?: false,
val struct = state.details[context]?.map ?: return
val wrapper = ObjectWrapper.Basic(struct)
Timber.d("Wrapper: $wrapper")
val space = wrapper.spaceId
if (space != null) {
dispatch(
ObjectSetCommand.Modal.Menu(
ctx = context,
space = space,
isArchived = state.details[context]?.isArchived ?: false,
isFavorite = state.details[context]?.isFavorite ?: false,
)
)
)
} else {
Timber.e("Space not found").also {
toast("Space not found")
}
}
}
fun onIconClicked() {
fun onObjectIconClicked() {
Timber.d("onIconClicked, ")
dispatch(ObjectSetCommand.Modal.OpenIconActionMenu(target = context))
val state = stateReducer.state.value.dataViewState() ?: return
val struct = state.details[context]
val wrapper = ObjectWrapper.Basic(struct?.map.orEmpty())
val space = wrapper.spaceId
if (space != null) {
dispatch(
ObjectSetCommand.Modal.OpenIconActionMenu(
target = context,
space = space
)
)
} else {
Timber.e("Space not found").also {
toast("Space not found")
}
}
}
fun onCoverClicked() {

View file

@ -17,6 +17,7 @@ import com.anytypeio.anytype.core_models.RelationFormat
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.core_models.ext.addIds
import com.anytypeio.anytype.core_models.ext.getValues
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.core_utils.ext.cancel
import com.anytypeio.anytype.core_utils.ext.typeOf
import com.anytypeio.anytype.domain.misc.UrlBuilder
@ -526,25 +527,33 @@ abstract class RelationValueBaseViewModel(
) {
viewModelScope.launch {
isLoading.emit(true)
val obj = values.get(ctx = ctx, target = target)
addFileToObject(
params = AddFileToObject.Params(
ctx = target,
relation = relationKey,
obj = obj,
path = filePath
val obj = ObjectWrapper.Basic(values.get(ctx = ctx, target = target))
val space = obj.spaceId
if (space != null) {
addFileToObject(
params = AddFileToObject.Params(
ctx = target,
relation = relationKey,
obj = obj.map,
path = filePath,
space = SpaceId(space)
)
).process(
failure = {
isLoading.emit(false)
Timber.e(it, "Error while adding new file to object")
},
success = {
isLoading.emit(false)
Timber.d("Successfully add new file to object")
dispatcher.send(it)
}
)
).process(
failure = {
isLoading.emit(false)
Timber.e(it, "Error while adding new file to object")
},
success = {
isLoading.emit(false)
Timber.d("Successfully add new file to object")
dispatcher.send(it)
} else {
Timber.e("Space not found for relation value").also {
sendToast("Space not found")
}
)
}
}
}

View file

@ -224,6 +224,16 @@ class SelectSpaceViewModel(
}
}
fun onProfileSettingsClicked() {
viewModelScope.launch {
commands.emit(
Command.NavigateToProfileSettings(
space = spaceManager.get()
)
)
}
}
private fun proceedWithUnsubscribing() {
viewModelScope.launch {
storelessSubscriptionContainer.unsubscribe(
@ -287,4 +297,5 @@ sealed class Command {
object CreateSpace : Command()
object Dismiss : Command()
object SwitchToNewSpace: Command()
data class NavigateToProfileSettings(val space: Id) : Command()
}

View file

@ -2,6 +2,7 @@ package com.anytypeio.anytype.presentation.types.icon_picker
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.emojifier.data.EmojiProvider
import com.anytypeio.anytype.emojifier.suggest.EmojiSuggester
import com.anytypeio.anytype.presentation.picker.IconPickerViewModel
@ -23,8 +24,8 @@ class TypeIconPickerViewModel(
actions.value = EmojiPickerAction.RemoveEmoji
}
override fun onPickedFromDevice(iconable: Unit, path: String) {
// do nothing
override fun onPickedFromDevice(iconable: Unit, path: String, space: Id) {
// Do nothing
}
sealed class EmojiPickerAction {

View file

@ -4,12 +4,14 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.ObjectTypeIds
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.core_models.restrictions.ObjectRestriction
import com.anytypeio.anytype.presentation.MockTypicalDocumentFactory.page
import com.anytypeio.anytype.presentation.MockTypicalDocumentFactory.profile
import com.anytypeio.anytype.presentation.util.CoroutinesTestRule
import com.anytypeio.anytype.test_utils.MockDataFactory
import com.jraska.livedata.test
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@ -52,14 +54,32 @@ class EditorMenuTest : EditorPresentationTestSetup() {
}
@Test
fun `should dispatch command for opening page menu if document is started`() {
fun `should dispatch command for opening page menu if document is started`() = runTest {
// SETUP
val space = MockDataFactory.randomUuid()
val doc = page(root)
val details = Block.Details(
mapOf(
root to Block.Fields(
mapOf(
Relations.ID to root,
Relations.SPACE_ID to space
)
)
)
)
stubInterceptEvents()
stubOpenDocument(document = doc)
stubOpenDocument(
document = doc,
details = details,
spaceId = SpaceId(space)
)
stubSpaceManager(space = space)
val vm = buildViewModel()
@ -79,22 +99,43 @@ class EditorMenuTest : EditorPresentationTestSetup() {
isFavorite = false,
isLocked = false,
fromName = "",
isTemplate = false
isTemplate = false,
space = space,
ctx = root
)
}
}
@Test
fun `should dispatch command for opening page menu with restrictions if document is started`() {
fun `should dispatch command for opening page menu with restrictions if document is started`() = runTest {
// SETUP
val space = MockDataFactory.randomUuid()
val details = Block.Details(
mapOf(
root to Block.Fields(
mapOf(
Relations.ID to root,
Relations.SPACE_ID to space
)
)
)
)
val doc = page(root)
val objectRestrictions = listOf(ObjectRestriction.LAYOUT_CHANGE, ObjectRestriction.DELETE)
stubInterceptEvents()
stubOpenDocument(document = doc, objectRestrictions = objectRestrictions)
stubOpenDocument(
document = doc,
objectRestrictions = objectRestrictions,
spaceId = SpaceId(space),
details = details
)
stubSpaceManager(space = space)
val vm = buildViewModel()
@ -114,7 +155,9 @@ class EditorMenuTest : EditorPresentationTestSetup() {
isFavorite = false,
isLocked = false,
fromName = "",
isTemplate = false
isTemplate = false,
space = space,
ctx = root
)
}
}
@ -143,26 +186,38 @@ class EditorMenuTest : EditorPresentationTestSetup() {
}
@Test
fun `should send open profile menu event`() {
fun `should send open profile menu event`() = runTest {
// SETUP
val space = MockDataFactory.randomUuid()
val doc = page(root)
val typeId = MockDataFactory.randomString()
val details =
Block.Details(
mapOf(
root to Block.Fields(
mapOf(Relations.TYPE to typeId)
),
typeId to Block.Fields(
mapOf(Relations.ID to typeId, Relations.UNIQUE_KEY to ObjectTypeIds.PROFILE)
val details = Block.Details(
mapOf(
root to Block.Fields(
mapOf(
Relations.ID to root,
Relations.SPACE_ID to space,
Relations.TYPE to typeId
)
),
typeId to Block.Fields(
mapOf(Relations.ID to typeId, Relations.UNIQUE_KEY to ObjectTypeIds.PROFILE)
)
)
)
stubInterceptEvents()
stubOpenDocument(document = doc, details = details)
stubOpenDocument(
document = doc,
details = details,
spaceId = SpaceId(space)
)
stubSpaceManager(space)
val vm = buildViewModel()
@ -182,7 +237,9 @@ class EditorMenuTest : EditorPresentationTestSetup() {
isLocked = false,
isArchived = false,
fromName = "",
isTemplate = false
isTemplate = false,
space = space,
ctx = root
)
}
}

View file

@ -435,7 +435,7 @@ fun ProfileImageBlock(
modifier = Modifier
.size(96.dp)
.clip(RoundedCornerShape(48.dp))
.background(colorResource(id = R.color.shape_primary))
.background(colorResource(id = R.color.text_tertiary))
.noRippleClickable {
onProfileIconClick.invoke()
}

View file

@ -7,7 +7,9 @@ import androidx.lifecycle.viewModelScope
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.analytics.base.EventsDictionary
import com.anytypeio.anytype.analytics.base.sendEvent
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.domain.account.DeleteAccount
import com.anytypeio.anytype.domain.base.BaseUseCase
import com.anytypeio.anytype.domain.base.fold
@ -132,15 +134,19 @@ class ProfileSettingsViewModel(
}
}
fun onPickedImageFromDevice(path: String) {
fun onPickedImageFromDevice(path: String, space: Id) {
viewModelScope.launch {
setImageIcon(
SetImageIcon.Params(target = profileId, path = path)
SetImageIcon.Params(
target = profileId,
path = path,
spaceId = SpaceId(space)
)
).process(
failure = {
Timber.e("Error while setting image icon")
},
success = { (payload, _) ->
success = {
// do nothing
}
)