diff --git a/app/src/androidTest/java/com/anytypeio/anytype/features/editor/SlashWidgetTesting.kt b/app/src/androidTest/java/com/anytypeio/anytype/features/editor/SlashWidgetTesting.kt index 5a1e02b1c0..2f7a0a8ac5 100644 --- a/app/src/androidTest/java/com/anytypeio/anytype/features/editor/SlashWidgetTesting.kt +++ b/app/src/androidTest/java/com/anytypeio/anytype/features/editor/SlashWidgetTesting.kt @@ -19,6 +19,7 @@ import com.anytypeio.anytype.core_models.Position import com.anytypeio.anytype.core_models.Relation import com.anytypeio.anytype.core_models.Relations import com.anytypeio.anytype.core_models.StubBookmark +import com.anytypeio.anytype.core_models.StubFile import com.anytypeio.anytype.core_ui.features.editor.slash.holders.MainMenuHolder import com.anytypeio.anytype.core_ui.features.editor.slash.holders.MediaMenuHolder import com.anytypeio.anytype.domain.block.interactor.CreateBlock @@ -526,14 +527,9 @@ class SlashWidgetTesting : EditorTestSetup() { val paragraph = paragraph(text = "FooBar") val paragraph2 = paragraph(text = "Second") - val file = Block( - id = MockDataFactory.randomUuid(), - fields = Block.Fields.empty(), - children = emptyList(), - content = Block.Content.File( - type = Block.Content.File.Type.FILE, - state = Block.Content.File.State.EMPTY - ) + val file = StubFile( + type = Block.Content.File.Type.FILE, + state = Block.Content.File.State.EMPTY ) val page = Block( @@ -599,14 +595,9 @@ class SlashWidgetTesting : EditorTestSetup() { val paragraph = paragraph(text = "FooBar") val paragraph2 = paragraph(text = "Second") - val picture = Block( - id = MockDataFactory.randomUuid(), - fields = Block.Fields.empty(), - children = emptyList(), - content = Block.Content.File( - type = Block.Content.File.Type.IMAGE, - state = Block.Content.File.State.EMPTY - ) + val picture = StubFile( + type = Block.Content.File.Type.IMAGE, + state = Block.Content.File.State.EMPTY ) val page = Block( @@ -678,14 +669,9 @@ class SlashWidgetTesting : EditorTestSetup() { val paragraph2 = paragraph(text = "Second") - val video = Block( - id = MockDataFactory.randomUuid(), - fields = Block.Fields.empty(), - children = emptyList(), - content = Block.Content.File( - type = Block.Content.File.Type.VIDEO, - state = Block.Content.File.State.EMPTY - ) + val video = StubFile( + type = Block.Content.File.Type.VIDEO, + state = Block.Content.File.State.EMPTY ) val page = Block( diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/onboarding/login/OnboardingMnemonicLoginDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/onboarding/login/OnboardingMnemonicLoginDI.kt index 982ce219cb..65b6d247a8 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/onboarding/login/OnboardingMnemonicLoginDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/onboarding/login/OnboardingMnemonicLoginDI.kt @@ -14,6 +14,7 @@ import com.anytypeio.anytype.domain.config.ConfigStorage import com.anytypeio.anytype.domain.config.UserSettingsRepository import com.anytypeio.anytype.domain.debugging.DebugAccountSelectTrace import com.anytypeio.anytype.domain.debugging.DebugGoroutines +import com.anytypeio.anytype.domain.debugging.Logger import com.anytypeio.anytype.domain.device.PathProvider import com.anytypeio.anytype.domain.misc.LocaleProvider import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider @@ -99,4 +100,5 @@ interface OnboardingMnemonicLoginDependencies : ComponentDependencies { fun spaceManager(): SpaceManager fun globalSubscriptionManager(): GlobalSubscriptionManager fun debugAccountSelectTrace(): DebugAccountSelectTrace + fun logger(): Logger } \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/spaces/SpaceSettingsDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/spaces/SpaceSettingsDI.kt index 4b3d17363e..6b80e69322 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/spaces/SpaceSettingsDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/spaces/SpaceSettingsDI.kt @@ -25,7 +25,6 @@ import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate import com.anytypeio.anytype.presentation.spaces.SpaceGradientProvider import com.anytypeio.anytype.presentation.spaces.SpaceSettingsViewModel import com.anytypeio.anytype.presentation.util.downloader.UriFileProvider -import com.anytypeio.anytype.providers.DefaultUriFileProvider import com.anytypeio.anytype.ui.settings.space.SpaceSettingsFragment import dagger.Binds import dagger.BindsInstance diff --git a/app/src/main/java/com/anytypeio/anytype/di/main/DataModule.kt b/app/src/main/java/com/anytypeio/anytype/di/main/DataModule.kt index 19ab47deea..cfd408f3a5 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/main/DataModule.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/main/DataModule.kt @@ -337,8 +337,9 @@ object DataModule { @Provides @Singleton fun provideFileProvider( - context: Context - ): UriFileProvider = DefaultUriFileProvider(context) + context: Context, + logger: Logger + ): UriFileProvider = DefaultUriFileProvider(context, logger) @JvmStatic @Provides diff --git a/app/src/main/java/com/anytypeio/anytype/providers/DefaultUriFileProvider.kt b/app/src/main/java/com/anytypeio/anytype/providers/DefaultUriFileProvider.kt index 23ebf32276..5b0aa4184d 100644 --- a/app/src/main/java/com/anytypeio/anytype/providers/DefaultUriFileProvider.kt +++ b/app/src/main/java/com/anytypeio/anytype/providers/DefaultUriFileProvider.kt @@ -4,19 +4,26 @@ import android.content.Context import android.net.Uri import androidx.core.content.FileProvider import com.anytypeio.anytype.BuildConfig +import com.anytypeio.anytype.domain.debugging.Logger import com.anytypeio.anytype.presentation.util.downloader.UriFileProvider import java.io.File import javax.inject.Inject class DefaultUriFileProvider @Inject constructor( - private val context: Context + private val context: Context, + private val logger: Logger ) : UriFileProvider { - override fun getUriForFile(file: File): Uri = FileProvider.getUriForFile( - context, - BuildConfig.APPLICATION_ID + PROVIDER, - file - ) + override fun getUriForFile(file: File): Uri { + logger.logInfo("DefaultUriFileProvider, start getting uri for file $file") + val contentUri = FileProvider.getUriForFile( + context, + BuildConfig.APPLICATION_ID + PROVIDER, + file + ) + logger.logInfo("DefaultUriFileProvider, got uri $contentUri") + return contentUri + } } private const val PROVIDER = ".provider" \ No newline at end of file diff --git a/app/src/main/res/xml/provider_paths.xml b/app/src/main/res/xml/provider_paths.xml index 948ac11c51..23ca145757 100644 --- a/app/src/main/res/xml/provider_paths.xml +++ b/app/src/main/res/xml/provider_paths.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/Block.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/Block.kt index c21a8ac31e..79c1ba1713 100644 --- a/core-models/src/main/java/com/anytypeio/anytype/core_models/Block.kt +++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/Block.kt @@ -86,6 +86,7 @@ data class Block( fun asText() = this as Text fun asLink() = this as Link + fun asFile() = this as File /** * Smart block. @@ -227,12 +228,13 @@ data class Block( * @property state file state */ data class File( - val targetObjectId: Id? = null, - val name: String? = null, - val mime: String? = null, - val size: Long? = null, - val type: Type? = null, - val state: State? = null + val targetObjectId: Id, + val name: String, + val mime: String, + val size: Long, + val type: Type, + val state: State, + val addedAt: Long ) : Content() { enum class Type { NONE, FILE, IMAGE, VIDEO, AUDIO, PDF } enum class State { EMPTY, UPLOADING, DONE, ERROR } diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/SupportedLayouts.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/SupportedLayouts.kt index 5b305ac115..0fff11d579 100644 --- a/core-models/src/main/java/com/anytypeio/anytype/core_models/SupportedLayouts.kt +++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/SupportedLayouts.kt @@ -75,4 +75,8 @@ object SupportedLayouts { fun isEditorOrFileLayout(layout: ObjectType.Layout?) : Boolean { return editorLayouts.contains(layout) || fileLayouts.contains(layout) } + + fun isFileLayout(layout: ObjectType.Layout?) : Boolean { + return fileLayouts.contains(layout) + } } \ No newline at end of file diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/BlockAdapter.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/BlockAdapter.kt index 4d865876b2..68cfeba72b 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/BlockAdapter.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/BlockAdapter.kt @@ -43,6 +43,7 @@ import com.anytypeio.anytype.core_ui.databinding.ItemBlockObjectLinkCardSmallIco import com.anytypeio.anytype.core_ui.databinding.ItemBlockObjectLinkCardSmallIconCoverBinding import com.anytypeio.anytype.core_ui.databinding.ItemBlockObjectLinkDeleteBinding import com.anytypeio.anytype.core_ui.databinding.ItemBlockObjectLinkLoadingBinding +import com.anytypeio.anytype.core_ui.databinding.ItemBlockOpenFileBinding import com.anytypeio.anytype.core_ui.databinding.ItemBlockPictureBinding import com.anytypeio.anytype.core_ui.databinding.ItemBlockRelationCheckboxBinding import com.anytypeio.anytype.core_ui.databinding.ItemBlockRelationDefaultBinding @@ -55,6 +56,7 @@ import com.anytypeio.anytype.core_ui.databinding.ItemBlockRelationTagBinding import com.anytypeio.anytype.core_ui.databinding.ItemBlockTableBinding import com.anytypeio.anytype.core_ui.databinding.ItemBlockTextBinding import com.anytypeio.anytype.core_ui.databinding.ItemBlockTitleBinding +import com.anytypeio.anytype.core_ui.databinding.ItemBlockTitleFileBinding import com.anytypeio.anytype.core_ui.databinding.ItemBlockTitleProfileBinding import com.anytypeio.anytype.core_ui.databinding.ItemBlockTitleTodoBinding import com.anytypeio.anytype.core_ui.databinding.ItemBlockTocBinding @@ -113,6 +115,8 @@ import com.anytypeio.anytype.core_ui.features.editor.holders.text.Text import com.anytypeio.anytype.core_ui.features.editor.holders.text.Toggle import com.anytypeio.anytype.core_ui.features.editor.holders.upload.BookmarkUpload import com.anytypeio.anytype.core_ui.features.editor.holders.upload.FileUpload +import com.anytypeio.anytype.core_ui.features.editor.holders.upload.OpenFile +import com.anytypeio.anytype.core_ui.features.editor.holders.upload.OpenImage import com.anytypeio.anytype.core_ui.features.editor.holders.upload.PictureUpload import com.anytypeio.anytype.core_ui.features.editor.holders.upload.VideoUpload import com.anytypeio.anytype.core_ui.features.table.holders.TableBlockHolder @@ -148,6 +152,7 @@ import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_FILE import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_FILE_ERROR import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_FILE_PLACEHOLDER +import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_FILE_TITLE import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_FILE_UPLOAD import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_HEADER_ONE import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_HEADER_THREE @@ -163,6 +168,8 @@ import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_OBJECT_LINK_DEFAULT import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_OBJECT_LINK_DELETED import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_OBJECT_LINK_LOADING +import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_BUTTON_OPEN_FILE +import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_BUTTON_OPEN_IMAGE import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_PARAGRAPH import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_PICTURE import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_PICTURE_ERROR @@ -358,6 +365,11 @@ class BlockAdapter( } } } + HOLDER_FILE_TITLE -> { + Title.File( + ItemBlockTitleFileBinding.inflate(inflater, parent, false) + ) + } HOLDER_TODO_TITLE -> { Title.Todo( ItemBlockTitleTodoBinding.inflate(inflater, parent, false) @@ -530,6 +542,12 @@ class BlockAdapter( ItemBlockMediaErrorBinding.inflate(inflater, parent, false) ) } + HOLDER_BUTTON_OPEN_FILE -> { + OpenFile(ItemBlockOpenFileBinding.inflate(inflater, parent, false)) + } + HOLDER_BUTTON_OPEN_IMAGE -> { + OpenImage(ItemBlockOpenFileBinding.inflate(inflater, parent, false)) + } HOLDER_VIDEO -> { Video( ItemBlockVideoBinding.inflate(inflater, parent, false) @@ -1002,6 +1020,12 @@ class BlockAdapter( item = blocks[position] as BlockView.Title.Todo ) } + is Title.File -> { + holder.processPayloads( + payloads = payloads.typeOf(), + item = blocks[position] as BlockView.Title.File + ) + } is Numbered -> { holder.processChangePayload( payloads = payloads.typeOf(), @@ -1361,6 +1385,11 @@ class BlockAdapter( holder.content.clipboardInterceptor = clipboardInterceptor } } + is Title.File -> { + holder.apply { + bind(item = blocks[position] as BlockView.Title.File,) + } + } is Code -> { holder.bind( item = blocks[position] as BlockView.Code, @@ -1622,6 +1651,18 @@ class BlockAdapter( clicked = onClickListener ) } + is OpenFile -> { + holder.bind( + item = blocks[position] as BlockView.ButtonOpenFile.FileButton, + click = onClickListener + ) + } + is OpenImage -> { + holder.bind( + item = blocks[position] as BlockView.ButtonOpenFile.ImageButton, + click = onClickListener + ) + } } if (holder is Text<*>) { diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/other/Title.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/other/Title.kt index 405207d125..442aa63fb7 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/other/Title.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/other/Title.kt @@ -3,11 +3,10 @@ package com.anytypeio.anytype.core_ui.features.editor.holders.other import android.text.Spannable import android.view.View import android.view.inputmethod.InputMethodManager +import android.widget.FrameLayout.LayoutParams import android.widget.ImageView import android.widget.TextView -import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.unit.dp import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.postDelayed import androidx.core.view.updateLayoutParams @@ -16,6 +15,7 @@ import com.anytypeio.anytype.core_ui.R import com.anytypeio.anytype.core_ui.common.SearchHighlightSpan import com.anytypeio.anytype.core_ui.common.SearchTargetHighlightSpan import com.anytypeio.anytype.core_ui.databinding.ItemBlockTitleBinding +import com.anytypeio.anytype.core_ui.databinding.ItemBlockTitleFileBinding import com.anytypeio.anytype.core_ui.databinding.ItemBlockTitleProfileBinding import com.anytypeio.anytype.core_ui.databinding.ItemBlockTitleTodoBinding import com.anytypeio.anytype.core_ui.extensions.setBlockBackgroundColor @@ -23,7 +23,7 @@ import com.anytypeio.anytype.core_ui.features.editor.BlockViewDiffUtil import com.anytypeio.anytype.core_ui.features.editor.BlockViewHolder import com.anytypeio.anytype.core_ui.features.editor.holders.`interface`.TextHolder import com.anytypeio.anytype.core_ui.tools.DefaultSpannableFactory -import com.anytypeio.anytype.core_ui.widgets.RadialGradientComposeView +import com.anytypeio.anytype.core_ui.widgets.ObjectIconWidget import com.anytypeio.anytype.core_ui.widgets.text.TextInputWidget import com.anytypeio.anytype.core_utils.ext.dimen import com.anytypeio.anytype.core_utils.ext.gone @@ -555,4 +555,37 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder { content.setBlockBackgroundColor(item.background) } } + + class File(val binding: ItemBlockTitleFileBinding) : Title(binding.root) { + + override val icon: ObjectIconWidget = binding.objectIconWidget + override val image: ImageView = binding.cover + override val selectionView: View = itemView + override val root: View = itemView + override val content: TextInputWidget = binding.title + + init { + icon.binding.ivImage.updateLayoutParams { + height = itemView.resources.getDimension(R.dimen.dp_80).toInt() + width = itemView.resources.getDimension(R.dimen.dp_64).toInt() + } + } + + fun bind( + item: BlockView.Title.File, + ) { + super.bind( + item = item, + onCoverClicked = {} + ) + icon.setIcon(item.icon) + } + + override fun applyTextColor(item: BlockView.Title) { + //do nothing + } + override fun applyBackground(item: BlockView.Title) { + //do nothing + } + } } \ No newline at end of file diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/upload/OpenFile.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/upload/OpenFile.kt new file mode 100644 index 0000000000..0f48c101e5 --- /dev/null +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/upload/OpenFile.kt @@ -0,0 +1,41 @@ +package com.anytypeio.anytype.core_ui.features.editor.holders.upload + +import android.view.View +import com.anytypeio.anytype.core_ui.databinding.ItemBlockOpenFileBinding +import com.anytypeio.anytype.core_ui.features.editor.BlockViewHolder +import com.anytypeio.anytype.presentation.editor.editor.listener.ListenerType +import com.anytypeio.anytype.presentation.editor.editor.model.BlockView + +class OpenFile( + binding: ItemBlockOpenFileBinding +) : BlockViewHolder(binding.root) { + + private val root: View = itemView + + fun bind(item: BlockView.ButtonOpenFile.FileButton, click: (ListenerType) -> Unit) { + root.setOnClickListener { + click( + ListenerType.File.View( + target = item.id, + ) + ) + } + } +} + +class OpenImage( + binding: ItemBlockOpenFileBinding +) : BlockViewHolder(binding.root) { + + private val root: View = itemView + + fun bind(item: BlockView.ButtonOpenFile.ImageButton, click: (ListenerType) -> Unit) { + root.setOnClickListener { + click( + ListenerType.Picture.View( + target = item.id + ) + ) + } + } +} \ No newline at end of file diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/ObjectIconWidget.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/ObjectIconWidget.kt index 2ade1ca5e8..fa82e469ae 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/ObjectIconWidget.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/ObjectIconWidget.kt @@ -144,7 +144,6 @@ class ObjectIconWidget @JvmOverloads constructor( is ObjectIcon.None -> removeIcon() is ObjectIcon.File -> setFileImage( mime = icon.mime, - fileName = icon.fileName, extension = icon.extensions ) ObjectIcon.Deleted -> setDeletedIcon() @@ -220,7 +219,7 @@ class ObjectIconWidget @JvmOverloads constructor( } } - private fun setFileImage(mime: String?, fileName: String?, extension: String?) { + private fun setFileImage(mime: String?, extension: String?) { val icon = mime.getMimeIcon(extension) with(binding) { ivImage.visible() diff --git a/core-ui/src/main/res/drawable/bg_button_open_file.xml b/core-ui/src/main/res/drawable/bg_button_open_file.xml new file mode 100644 index 0000000000..44dcebe59f --- /dev/null +++ b/core-ui/src/main/res/drawable/bg_button_open_file.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/core-ui/src/main/res/drawable/bg_title_file_icon.xml b/core-ui/src/main/res/drawable/bg_title_file_icon.xml new file mode 100644 index 0000000000..e95165e6d5 --- /dev/null +++ b/core-ui/src/main/res/drawable/bg_title_file_icon.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/core-ui/src/main/res/layout/item_block_open_file.xml b/core-ui/src/main/res/layout/item_block_open_file.xml new file mode 100644 index 0000000000..4cc3982984 --- /dev/null +++ b/core-ui/src/main/res/layout/item_block_open_file.xml @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/core-ui/src/main/res/layout/item_block_title_file.xml b/core-ui/src/main/res/layout/item_block_title_file.xml new file mode 100644 index 0000000000..c5b76daec0 --- /dev/null +++ b/core-ui/src/main/res/layout/item_block_title_file.xml @@ -0,0 +1,50 @@ + + + + + + + + + + \ No newline at end of file diff --git a/core-ui/src/main/res/values/dimens.xml b/core-ui/src/main/res/values/dimens.xml index 5880304c71..f78c571f44 100644 --- a/core-ui/src/main/res/values/dimens.xml +++ b/core-ui/src/main/res/values/dimens.xml @@ -35,6 +35,7 @@ 46dp 48dp 54dp + 64dp 72dp 80dp 51dp diff --git a/domain/src/test/java/com/anytypeio/anytype/domain/auth/CreateAccountTest.kt b/domain/src/test/java/com/anytypeio/anytype/domain/auth/CreateAccountTest.kt index dde7854031..bf909ac8fe 100644 --- a/domain/src/test/java/com/anytypeio/anytype/domain/auth/CreateAccountTest.kt +++ b/domain/src/test/java/com/anytypeio/anytype/domain/auth/CreateAccountTest.kt @@ -20,12 +20,10 @@ import org.junit.Rule import org.junit.Test import org.mockito.Mock import org.mockito.MockitoAnnotations -import org.mockito.kotlin.any import org.mockito.kotlin.doReturn import org.mockito.kotlin.stub import org.mockito.kotlin.times import org.mockito.kotlin.verify -import org.mockito.kotlin.verifyNoMoreInteractions class CreateAccountTest { diff --git a/domain/src/test/java/com/anytypeio/anytype/domain/ext/BlockExtensionTest.kt b/domain/src/test/java/com/anytypeio/anytype/domain/ext/BlockExtensionTest.kt index b925849b3c..888a879053 100644 --- a/domain/src/test/java/com/anytypeio/anytype/domain/ext/BlockExtensionTest.kt +++ b/domain/src/test/java/com/anytypeio/anytype/domain/ext/BlockExtensionTest.kt @@ -1,6 +1,7 @@ package com.anytypeio.anytype.domain.ext import com.anytypeio.anytype.core_models.Block +import com.anytypeio.anytype.core_models.StubFile import com.anytypeio.anytype.core_models.ext.asMap import com.anytypeio.anytype.core_models.ext.asRender import com.anytypeio.anytype.core_models.ext.getChildrenIdsList @@ -1096,19 +1097,9 @@ class BlockExtensionTest { val root = MockDataFactory.randomUuid() - val a = Block( - id = MockDataFactory.randomUuid(), - fields = Block.Fields.empty(), - children = emptyList(), - content = Block.Content.File() - ) + val a = StubFile() - val b = Block( - id = MockDataFactory.randomUuid(), - fields = Block.Fields.empty(), - children = emptyList(), - content = Block.Content.File() - ) + val b = StubFile() val document = listOf(a, b) diff --git a/localization/src/main/res/values/strings.xml b/localization/src/main/res/values/strings.xml index 8ad4844670..ec8d3b0892 100644 --- a/localization/src/main/res/values/strings.xml +++ b/localization/src/main/res/values/strings.xml @@ -525,6 +525,7 @@ Open source Reload object content Open link + Open file Copy email Copy phone number Send email diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToCoreModelMappers.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToCoreModelMappers.kt index 0a7306c26b..5f426fef8c 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToCoreModelMappers.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToCoreModelMappers.kt @@ -376,7 +376,8 @@ fun MBlock.toCoreModelsFile(): Block.Content.File { mime = content.mime, size = content.size, type = content.type.toCoreModels(), - state = content.state.toCoreModels() + state = content.state.toCoreModels(), + addedAt = content.addedAt ) } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt index e067c1a86b..a0a324d2b4 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt @@ -5,7 +5,6 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope import com.anytypeio.anytype.analytics.base.Analytics import com.anytypeio.anytype.analytics.base.EventsDictionary -import com.anytypeio.anytype.analytics.base.EventsDictionary.Routes.objDate import com.anytypeio.anytype.analytics.base.EventsDictionary.searchScreenShow import com.anytypeio.anytype.analytics.base.EventsPropertiesKey import com.anytypeio.anytype.analytics.base.sendEvent @@ -22,7 +21,6 @@ import com.anytypeio.anytype.core_models.InternalFlags import com.anytypeio.anytype.core_models.Key import com.anytypeio.anytype.core_models.Marketplace.COLLECTION_MARKETPLACE_ID import com.anytypeio.anytype.core_models.Marketplace.SET_MARKETPLACE_ID -import com.anytypeio.anytype.core_models.MarketplaceObjectTypeIds import com.anytypeio.anytype.core_models.ObjectType import com.anytypeio.anytype.core_models.ObjectTypeIds import com.anytypeio.anytype.core_models.ObjectTypeUniqueKeys @@ -83,7 +81,6 @@ import com.anytypeio.anytype.domain.event.interactor.SpaceSyncAndP2PStatusProvid import com.anytypeio.anytype.domain.icon.SetDocumentImageIcon import com.anytypeio.anytype.domain.icon.SetImageIcon import com.anytypeio.anytype.domain.launch.GetDefaultObjectType -import com.anytypeio.anytype.domain.library.StoreSearchByIdsParams import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer import com.anytypeio.anytype.domain.misc.DateProvider import com.anytypeio.anytype.domain.misc.UrlBuilder @@ -223,7 +220,6 @@ import com.anytypeio.anytype.presentation.extension.sendAnalyticsObjectShowEvent import com.anytypeio.anytype.presentation.extension.sendAnalyticsObjectTypeSelectOrChangeEvent import com.anytypeio.anytype.presentation.extension.sendAnalyticsOpenAsObject import com.anytypeio.anytype.presentation.extension.sendAnalyticsRelationEvent -import com.anytypeio.anytype.presentation.extension.sendAnalyticsSearchResultEvent import com.anytypeio.anytype.presentation.extension.sendAnalyticsSearchWordsEvent import com.anytypeio.anytype.presentation.extension.sendAnalyticsSelectTemplateEvent import com.anytypeio.anytype.presentation.extension.sendAnalyticsSelectionMenuEvent @@ -233,7 +229,6 @@ import com.anytypeio.anytype.presentation.extension.sendAnalyticsSlashMenuEvent import com.anytypeio.anytype.presentation.extension.sendAnalyticsStyleMenuEvent import com.anytypeio.anytype.presentation.extension.sendAnalyticsUpdateTextMarkupEvent import com.anytypeio.anytype.presentation.extension.sendHideKeyboardEvent -import com.anytypeio.anytype.presentation.home.HomeScreenViewModel.Companion.HOME_SCREEN_PROFILE_OBJECT_SUBSCRIPTION import com.anytypeio.anytype.presentation.home.OpenObjectNavigation import com.anytypeio.anytype.presentation.home.navigation import com.anytypeio.anytype.presentation.mapper.mark @@ -259,13 +254,13 @@ import com.anytypeio.anytype.presentation.editor.model.OnEditorDatePickerEvent.O import com.anytypeio.anytype.presentation.editor.model.OnEditorDatePickerEvent.OnDateSelected import com.anytypeio.anytype.presentation.editor.model.OnEditorDatePickerEvent.OnTodayClick import com.anytypeio.anytype.presentation.editor.model.OnEditorDatePickerEvent.OnTomorrowClick +import com.anytypeio.anytype.presentation.extension.getFileDetailsForBlock +import com.anytypeio.anytype.presentation.extension.getUrlForFileContent import com.anytypeio.anytype.presentation.objects.getCreateObjectParams import com.anytypeio.anytype.presentation.objects.getObjectTypeViewsForSBPage import com.anytypeio.anytype.presentation.objects.getProperType import com.anytypeio.anytype.presentation.objects.isTemplatesAllowed import com.anytypeio.anytype.presentation.objects.toViews -import com.anytypeio.anytype.presentation.profile.ProfileIconView -import com.anytypeio.anytype.presentation.profile.profileIcon import com.anytypeio.anytype.presentation.relations.ObjectRelationView import com.anytypeio.anytype.presentation.relations.getNotIncludedRecommendedRelations import com.anytypeio.anytype.presentation.relations.getObjectRelations @@ -296,8 +291,6 @@ import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -366,8 +359,6 @@ class EditorViewModel( val actions = MutableStateFlow(ActionItemType.defaultSorting) - val icon = MutableStateFlow(ProfileIconView.Loading) - val isUndoEnabled = MutableStateFlow(false) val isRedoEnabled = MutableStateFlow(false) val isUndoRedoToolbarIsVisible = MutableStateFlow(false) @@ -442,7 +433,6 @@ class EditorViewModel( init { Timber.i("EditorViewModel, init") proceedWithObservingPermissions() - proceedWithObservingProfileIcon() startHandlingTextChanges() startProcessingFocusChanges() startProcessingControlPanelViewState() @@ -489,35 +479,6 @@ class EditorViewModel( } } - private fun proceedWithObservingProfileIcon() { - viewModelScope.launch { - spaceManager - .observe() - .flatMapLatest { config -> - storelessSubscriptionContainer.subscribe( - StoreSearchByIdsParams( - space = SpaceId(config.techSpace), - subscription = HOME_SCREEN_PROFILE_OBJECT_SUBSCRIPTION, - targets = listOf(config.profile), - keys = listOf( - Relations.ID, - Relations.NAME, - Relations.ICON_EMOJI, - Relations.ICON_IMAGE, - Relations.ICON_OPTION - ) - ) - ).map { result -> - val obj = result.firstOrNull() - obj?.profileIcon(urlBuilder) ?: ProfileIconView.Placeholder(null) - } - } - .catch { Timber.e(it, "Error while observing space icon") } - .flowOn(dispatchers.io) - .collect { icon.value = it } - } - } - override fun onPickedDocImageFromDevice(ctx: Id, path: String) { viewModelScope.launch { val obj = orchestrator.stores.details.getAsObject(ctx) @@ -824,6 +785,7 @@ class EditorViewModel( val flags = mutableListOf() Timber.d("Rendering starting...") val doc = models.asMap().render( + context = context, mode = mode, root = root, focus = focus, @@ -3827,8 +3789,8 @@ class EditorViewModel( } is ListenerType.File.View -> { when (mode) { - EditorMode.Edit -> onFileClicked(clicked.target) - EditorMode.Locked, EditorMode.Read -> onFileClicked(clicked.target) + EditorMode.Edit -> onFileBlockClicked(clicked.target) + EditorMode.Locked, EditorMode.Read -> onFileBlockClicked(clicked.target) EditorMode.Select -> onBlockMultiSelectClicked(clicked.target) else -> Unit } @@ -4284,85 +4246,76 @@ class EditorViewModel( } } - private fun onFileClicked(blockId: String) { - val fileBlock = blocks.find { it.id == blockId } - val url = urlBuilder.getUrlForFileBlock( - fileBlock = fileBlock - ) - if (url != null) { - dispatch( - Command.OpenFileByDefaultApp( - id = blockId, - uri = url - ) + private fun onFileBlockClicked(blockId: String) { + dispatch( + Command.OpenFileByDefaultApp( + id = blockId ) - } else { - Timber.e("Block is not File or with wrong state, can't proceed with open") - sendToast("Something went wrong. Couldn't open file.") - } + ) } fun startSharingFile(id: String, onDownloaded: (Uri) -> Unit = {}) { - - Timber.d("startDownloadingFile, id:[$id]") - + Timber.d("startSharingFile, fileBlockId: [$id]") sendToast("Preparing file to share...") - val block = blocks.firstOrNull { it.id == id } - val content = block?.content + val fileDetails = blocks.getFileDetailsForBlock(id, orchestrator, fieldParser) ?: return + val (content, targetObjectId, fileName) = fileDetails - if (content is Content.File && content.state == Content.File.State.DONE) { - viewModelScope.launch { - orchestrator.proxies.intents.send( - Media.ShareFile( - objectId = content.targetObjectId.orEmpty(), - name = content.name.orEmpty(), - type = content.type, - onDownloaded = onDownloaded - ) + Timber.d("startSharingFile, fileObjectId: [$targetObjectId], fileName: [$fileName]") + + viewModelScope.launch { + orchestrator.proxies.intents.send( + Media.ShareFile( + objectId = targetObjectId, + name = fileName, + type = content.type, + onDownloaded = onDownloaded ) - } - } else { - Timber.e("Block is not File or with wrong state, can't proceed with share!") + ) } } - fun startDownloadingFileFromBlock(blockId: Id) { + fun startDownloadingFileFromBlock(id: Id) { + Timber.d("startDownloadingFile, for block:[$id]") + sendToast("Downloading file in background...") - Timber.d("startDownloadingFile, for block:[$blockId]") + val fileDetails = blocks.getFileDetailsForBlock(id, orchestrator, fieldParser) ?: return + val (content, targetObjectId, fileName) = fileDetails - sendToast("Downloading file in background...") + val url = urlBuilder.getUrlForFileContent( + fileContent = content, + isOriginalImage = true + ) + + Timber.d("startDownloadingFileFromBlock, fileObjectId: [$targetObjectId], fileName: [$fileName], url: [$url]") - val fileBlock = blocks.firstOrNull { it.id == blockId } - val fileContent = fileBlock?.content as? Content.File - val url = urlBuilder.getUrlForFileBlock(fileBlock) - - if (fileContent != null && url != null) { + if (url != null) { viewModelScope.launch { orchestrator.proxies.intents.send( Media.DownloadFile( url = url, - name = fileContent.name.orEmpty(), - type = fileContent.type + name = fileName, + type = content.type ) ) } } else { - Timber.e("Block is not File or with wrong state, can't proceed with download") + Timber.e("Couldn't proceed with downloading file, because url is null") sendToast("Something went wrong. Couldn't download file.") } } private fun proceedWithDownloadCurrentObjectAsFile() { - val details = orchestrator.stores.details.current() - val objectDetails = details.details[context]?.map ?: return - if (objectDetails.isEmpty()) return - val obj = ObjectWrapper.Basic(objectDetails) + val fileObject = orchestrator.stores.details.getAsObject(target = context) + if (fileObject == null) { + Timber.e("Object with id $context not found.") + return + } - Timber.d("startDownloadingFileAsObject, for object:[$obj]") + Timber.d("startDownloadingFileAsObject, for object:[$context]") - val layout = obj.layout + val layout = fileObject.layout if (layout == null || layout !in SupportedLayouts.fileLayouts) { Timber.e("Object with layout:$layout is not Media, can't proceed with download") @@ -4373,7 +4326,7 @@ class EditorViewModel( sendToast("Downloading file in background...") val url = urlBuilder.getUrlBasedOnFileLayout( - obj = obj.id, + obj = fileObject.id, layout = layout ) @@ -4382,7 +4335,7 @@ class EditorViewModel( orchestrator.proxies.intents.send( Media.DownloadFile( url = url, - name = fieldParser.getObjectName(obj), + name = fieldParser.getObjectName(fileObject), type = null ) ) diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Command.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Command.kt index a3dee935fc..b1c51f9672 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Command.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Command.kt @@ -33,8 +33,7 @@ sealed class Command { * @property [id] id of the file block */ data class OpenFileByDefaultApp( - val id: String, - val uri: String + val id: String ) : Command() data class OpenObjectSnackbar( diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/ext/BlockViewExt.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/ext/BlockViewExt.kt index 4f8cb14f91..c54ddf6e03 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/ext/BlockViewExt.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/ext/BlockViewExt.kt @@ -155,6 +155,9 @@ fun List.singleStylingMode( is BlockView.Title.Todo -> view.copy( mode = BlockView.Mode.READ ) + is BlockView.Title.File -> view.copy( + mode = BlockView.Mode.READ + ) is BlockView.Title.Profile -> view.copy( mode = BlockView.Mode.READ ) @@ -312,6 +315,9 @@ fun List.enterSAM( is BlockView.Title.Todo -> view.copy( mode = BlockView.Mode.READ ) + is BlockView.Title.File -> view.copy( + mode = BlockView.Mode.READ + ) is BlockView.Title.Archive -> view.copy( mode = BlockView.Mode.READ ) @@ -494,6 +500,7 @@ fun List.updateCursorAndEditMode( ) is BlockView.Title.Basic -> view.copy(mode = BlockView.Mode.EDIT) is BlockView.Title.Todo -> view.copy(mode = BlockView.Mode.EDIT) + is BlockView.Title.File -> view.copy(mode = BlockView.Mode.EDIT) is BlockView.Title.Profile -> view.copy(mode = BlockView.Mode.EDIT) is BlockView.Title.Archive -> view.copy(mode = BlockView.Mode.EDIT) else -> view.also { @@ -516,6 +523,7 @@ fun List.toReadMode(): List = map { view -> is BlockView.Text.Callout -> view.copy(mode = BlockView.Mode.READ) is BlockView.Title.Basic -> view.copy(mode = BlockView.Mode.READ) is BlockView.Title.Todo -> view.copy(mode = BlockView.Mode.READ) + is BlockView.Title.File -> view.copy(mode = BlockView.Mode.READ) is BlockView.Title.Profile -> view.copy(mode = BlockView.Mode.READ) is BlockView.Title.Archive -> view.copy(mode = BlockView.Mode.READ) is BlockView.Description -> view.copy(mode = BlockView.Mode.READ) @@ -585,6 +593,7 @@ fun List.toEditMode(): List = map { view -> is BlockView.Title.Basic -> view.copy(mode = BlockView.Mode.EDIT) is BlockView.Title.Profile -> view.copy(mode = BlockView.Mode.EDIT) is BlockView.Title.Todo -> view.copy(mode = BlockView.Mode.EDIT) + is BlockView.Title.File -> view.copy(mode = BlockView.Mode.EDIT) is BlockView.Title.Archive -> view.copy(mode = BlockView.Mode.EDIT) else -> view.also { check(view !is BlockView.Permission) } } @@ -605,6 +614,7 @@ fun List.clearSearchHighlights(): List = map { view -> is BlockView.Title.Basic -> view.copy(searchFields = emptyList()) is BlockView.Title.Profile -> view.copy(searchFields = emptyList()) is BlockView.Title.Todo -> view.copy(searchFields = emptyList()) + is BlockView.Title.File -> view.copy(searchFields = emptyList()) is BlockView.Media.Bookmark -> view.copy(searchFields = emptyList()) is BlockView.Media.File -> view.copy(searchFields = emptyList()) is BlockView.LinkToObject.Default.Text -> view.copy(searchFields = emptyList()) @@ -674,6 +684,10 @@ fun List.highlight( val fields = listOf(DEFAULT_SEARCH_FIELD_KEY to view.text.orEmpty()) view.copy(searchFields = highlighter(fields)) } + is BlockView.Title.File -> { + val fields = listOf(DEFAULT_SEARCH_FIELD_KEY to view.text.orEmpty()) + view.copy(searchFields = highlighter(fields)) + } is BlockView.Title.Profile -> { val fields = listOf(DEFAULT_SEARCH_FIELD_KEY to view.text.orEmpty()) view.copy(searchFields = highlighter(fields)) @@ -781,6 +795,7 @@ fun BlockView.setHighlight( is BlockView.Title.Basic -> copy(searchFields = highlights) is BlockView.Title.Profile -> copy(searchFields = highlights) is BlockView.Title.Todo -> copy(searchFields = highlights) + is BlockView.Title.File -> copy(searchFields = highlights) is BlockView.Media.Bookmark -> copy(searchFields = highlights) is BlockView.Media.File -> copy(searchFields = highlights) is BlockView.LinkToObject.Default.Text -> copy(searchFields = highlights) diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/model/BlockView.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/model/BlockView.kt index 0dff6a24f5..533f32e42f 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/model/BlockView.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/model/BlockView.kt @@ -27,6 +27,7 @@ import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_FILE import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_FILE_ERROR import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_FILE_PLACEHOLDER +import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_FILE_TITLE import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_FILE_UPLOAD import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_HEADER_ONE import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_HEADER_THREE @@ -43,9 +44,9 @@ import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_OBJECT_LINK_DELETED import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_OBJECT_LINK_LOADING import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_OBJECT_TYPE -import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_OBJECT_TYPE_COLLECTION import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_OBJECT_TYPE_DELETED -import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_OBJECT_TYPE_SET +import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_BUTTON_OPEN_FILE +import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_BUTTON_OPEN_IMAGE import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_PARAGRAPH import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_PICTURE import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_PICTURE_ERROR @@ -75,7 +76,6 @@ import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER import com.anytypeio.anytype.presentation.objects.ObjectIcon import com.anytypeio.anytype.presentation.objects.appearance.choose.ObjectAppearanceChooseSettingsView import com.anytypeio.anytype.presentation.relations.ObjectRelationView -import com.anytypeio.anytype.presentation.spaces.SpaceIconView /** * UI-models for different types of blocks. @@ -668,6 +668,31 @@ sealed class BlockView : ViewType { override fun getViewType() = HOLDER_TITLE } + /** + * UI-model for a file-layout title block. + * @property id block's id + * @property text text content (i.e. title text) + */ + data class File( + override val id: String, + override var isFocused: Boolean = false, + override var text: String, + override var coverColor: CoverColor? = null, + override var coverImage: Url? = null, + override var coverGradient: String? = null, + override val background: ThemeColor = ThemeColor.DEFAULT, + override val color: ThemeColor = ThemeColor.DEFAULT, + val emoji: String? = null, + override val image: String? = null, + override val mode: Mode = Mode.READ, + override var cursor: Int? = null, + override val searchFields: List = emptyList(), + override val hint: String? = null, + val icon: ObjectIcon + ) : Title(), Searchable { + override fun getViewType() = HOLDER_FILE_TITLE + } + /** * UI-model for a profile-layout title block. * @property id block's id @@ -1268,6 +1293,28 @@ sealed class BlockView : ViewType { override fun getViewType(): Int = HOLDER_FEATURED_RELATION } + sealed class ButtonOpenFile( + override val id: String, + val isSelected: Boolean = false + ) : BlockView() { + + abstract val targetId: Id? + + data class ImageButton( + override val id: String, + override val targetId: Id + ) : ButtonOpenFile(id) { + override fun getViewType(): Int = HOLDER_BUTTON_OPEN_IMAGE + } + + data class FileButton( + override val id: String, + override val targetId: Id + ) : ButtonOpenFile(id) { + override fun getViewType(): Int = HOLDER_BUTTON_OPEN_FILE + } + } + sealed class Relation : BlockView(), Selectable, Indentable, Decoratable { abstract val background: ThemeColor diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/model/types/Types.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/model/types/Types.kt index d34e1ecaa7..c1fb63c24a 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/model/types/Types.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/model/types/Types.kt @@ -5,6 +5,7 @@ object Types { const val HOLDER_TITLE = 1 const val HOLDER_PROFILE_TITLE = 35 const val HOLDER_ARCHIVE_TITLE = 36 + const val HOLDER_FILE_TITLE = 37 const val HOLDER_TODO_TITLE = 48 const val HOLDER_HEADER_ONE = 2 const val HOLDER_HEADER_TWO = 3 @@ -45,6 +46,8 @@ object Types { const val HOLDER_FILE_PLACEHOLDER = 32 const val HOLDER_FILE_UPLOAD = 33 const val HOLDER_FILE_ERROR = 34 + const val HOLDER_BUTTON_OPEN_FILE = 134 + const val HOLDER_BUTTON_OPEN_IMAGE = 135 const val HOLDER_DIVIDER_LINE = 16 const val HOLDER_DIVIDER_DOTS = 38 diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/render/BlockViewRenderer.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/render/BlockViewRenderer.kt index c636943ab5..3def4602fa 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/render/BlockViewRenderer.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/render/BlockViewRenderer.kt @@ -15,12 +15,14 @@ interface BlockViewRenderer { /** * Ext. function for recursively converting map to flattened view data structure. + * @param context object id * @param root root block, from which rendering starts * @param focus id of the current focus * @param anchor id of the current anchor (current rendering node) * @param indent current indent at this rendering node. */ suspend fun Map>.render( + context: Id, mode: EditorMode = EditorMode.Edit, root: Block, focus: Editor.Focus, diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/render/DefaultBlockViewRenderer.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/render/DefaultBlockViewRenderer.kt index 7947c3aea5..8943c5f494 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/render/DefaultBlockViewRenderer.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/render/DefaultBlockViewRenderer.kt @@ -9,7 +9,6 @@ import com.anytypeio.anytype.core_models.ObjectTypeIds.BOOKMARK import com.anytypeio.anytype.core_models.ObjectWrapper import com.anytypeio.anytype.core_models.RelationLink import com.anytypeio.anytype.core_models.Relations -import com.anytypeio.anytype.core_models.SupportedLayouts import com.anytypeio.anytype.core_models.ThemeColor import com.anytypeio.anytype.core_models.ext.parseThemeTextColor import com.anytypeio.anytype.core_models.ext.textColor @@ -58,6 +57,7 @@ class DefaultBlockViewRenderer @Inject constructor( ) : BlockViewRenderer, ToggleStateHolder by toggleStateHolder { override suspend fun Map>.render( + context: Id, mode: EditorMode, root: Block, focus: Focus, @@ -119,6 +119,7 @@ class DefaultBlockViewRenderer @Inject constructor( if (block.children.isNotEmpty()) { result.addAll( render( + context = context, mode = mode, root = root, focus = focus, @@ -164,6 +165,7 @@ class DefaultBlockViewRenderer @Inject constructor( if (block.children.isNotEmpty()) { result.addAll( render( + context = context, mode = mode, root = root, focus = focus, @@ -201,6 +203,7 @@ class DefaultBlockViewRenderer @Inject constructor( if (toggleStateHolder.isToggled(block.id)) { result.addAll( render( + context = context, mode = mode, root = root, focus = focus, @@ -241,6 +244,7 @@ class DefaultBlockViewRenderer @Inject constructor( if (block.children.isNotEmpty()) { result.addAll( render( + context = context, mode = mode, root = root, focus = focus, @@ -281,6 +285,7 @@ class DefaultBlockViewRenderer @Inject constructor( if (block.children.isNotEmpty()) { result.addAll( render( + context = context, mode = mode, root = root, focus = focus, @@ -321,6 +326,7 @@ class DefaultBlockViewRenderer @Inject constructor( if (block.children.isNotEmpty()) { result.addAll( render( + context = context, mode = mode, root = root, focus = focus, @@ -364,6 +370,7 @@ class DefaultBlockViewRenderer @Inject constructor( if (block.children.isNotEmpty()) { result.addAll( render( + context = context, mode = mode, root = root, focus = focus, @@ -400,6 +407,7 @@ class DefaultBlockViewRenderer @Inject constructor( if (block.children.isNotEmpty()) { result.addAll( render( + context = context, mode = mode, root = root, focus = focus, @@ -458,6 +466,7 @@ class DefaultBlockViewRenderer @Inject constructor( if (block.children.isNotEmpty()) { result.addAll( render( + context = context, mode = mode, root = root, focus = focus, @@ -497,6 +506,7 @@ class DefaultBlockViewRenderer @Inject constructor( if (block.children.isNotEmpty()) { result.addAll( render( + context = context, mode = mode, root = root, focus = focus, @@ -541,6 +551,7 @@ class DefaultBlockViewRenderer @Inject constructor( if (block.children.isNotEmpty()) { result.addAll( render( + context = context, mode = mode, root = root, focus = focus, @@ -623,11 +634,6 @@ class DefaultBlockViewRenderer @Inject constructor( isPreviousBlockMedia = link is BlockView.LinkToObject.Default.Card } is Content.File -> { - val detail = details.details.getOrDefault(root.id, Block.Fields.empty()) - val obj = ObjectWrapper.Basic(detail.map) - if (SupportedLayouts.fileLayouts.contains(obj.layout)) { - return@forEach - } mCounter = 0 val blockDecorationScheme = buildNestedDecorationData( block = block, @@ -640,18 +646,19 @@ class DefaultBlockViewRenderer @Inject constructor( background = block.parseThemeBackgroundColor() ) ) - result.add( - file( - mode = mode, - content = content, - block = block, - indent = indent, - selection = selection, - isPreviousBlockMedia = isPreviousBlockMedia, - schema = blockDecorationScheme, - details = details - ) + val fileBlock = file( + context = context, + mode = mode, + content = content, + block = block, + indent = indent, + selection = selection, + isPreviousBlockMedia = isPreviousBlockMedia, + schema = blockDecorationScheme, + details = details, + fieldParser = fieldParser ) + result.add(fileBlock) isPreviousBlockMedia = true } is Content.Layout -> { @@ -666,6 +673,7 @@ class DefaultBlockViewRenderer @Inject constructor( } result.addAll( render( + context = context, mode = mode, root = root, focus = focus, @@ -1376,6 +1384,7 @@ class DefaultBlockViewRenderer @Inject constructor( } private fun file( + context: Id, mode: EditorMode, content: Content.File, block: Block, @@ -1383,99 +1392,21 @@ class DefaultBlockViewRenderer @Inject constructor( selection: Set, isPreviousBlockMedia: Boolean, schema: NestedDecorationData, - details: Block.Details - ): BlockView = when (content.type) { - Content.File.Type.IMAGE -> content.toPictureView( - blockId = block.id, - urlBuilder = urlBuilder, - indent = indent, - mode = if (mode == EditorMode.Edit) BlockView.Mode.EDIT else BlockView.Mode.READ, - isSelected = checkIfSelected( - mode = mode, - block = block, - selection = selection - ), - background = block.parseThemeBackgroundColor(), - isPreviousBlockMedia = isPreviousBlockMedia, - decorations = schema.toBlockViewDecoration(block), - details = details - ) - Content.File.Type.FILE -> content.toFileView( - blockId = block.id, - urlBuilder = urlBuilder, - indent = indent, - mode = if (mode == EditorMode.Edit) BlockView.Mode.EDIT else BlockView.Mode.READ, - isSelected = checkIfSelected( - mode = mode, - block = block, - selection = selection - ), - background = block.parseThemeBackgroundColor(), - isPrevBlockMedia = isPreviousBlockMedia, - decorations = schema.toBlockViewDecoration(block), - details = details - ) - Content.File.Type.VIDEO -> content.toVideoView( - blockId = block.id, - urlBuilder = urlBuilder, - indent = indent, - mode = if (mode == EditorMode.Edit) BlockView.Mode.EDIT else BlockView.Mode.READ, - isSelected = checkIfSelected( - mode = mode, - block = block, - selection = selection - ), - background = block.parseThemeBackgroundColor(), - isPrevBlockMedia = isPreviousBlockMedia, - decorations = schema.toBlockViewDecoration(block), - details = details - ) - Content.File.Type.AUDIO -> content.toFileView( - blockId = block.id, - urlBuilder = urlBuilder, - indent = indent, - mode = if (mode == EditorMode.Edit) BlockView.Mode.EDIT else BlockView.Mode.READ, - isSelected = checkIfSelected( - mode = mode, - block = block, - selection = selection - ), - background = block.parseThemeBackgroundColor(), - isPrevBlockMedia = isPreviousBlockMedia, - decorations = schema.toBlockViewDecoration(block), - details = details - ) - Content.File.Type.PDF -> content.toFileView( - blockId = block.id, - urlBuilder = urlBuilder, - indent = indent, - mode = if (mode == EditorMode.Edit) BlockView.Mode.EDIT else BlockView.Mode.READ, - isSelected = checkIfSelected( - mode = mode, - block = block, - selection = selection - ), - background = block.parseThemeBackgroundColor(), - isPrevBlockMedia = isPreviousBlockMedia, - decorations = schema.toBlockViewDecoration(block), - details = details - ) - Content.File.Type.NONE -> content.toFileView( - blockId = block.id, - urlBuilder = urlBuilder, - indent = indent, - mode = if (mode == EditorMode.Edit) BlockView.Mode.EDIT else BlockView.Mode.READ, - isSelected = checkIfSelected( - mode = mode, - block = block, - selection = selection - ), - background = block.parseThemeBackgroundColor(), - isPrevBlockMedia = isPreviousBlockMedia, - decorations = schema.toBlockViewDecoration(block), - details = details - ) - else -> throw IllegalStateException("Unexpected file type: ${content.type}") + details: Block.Details, + fieldParser: FieldParser + ): BlockView { + + val blockViewMode = + if (mode == EditorMode.Edit) BlockView.Mode.EDIT else BlockView.Mode.READ + val isSelected = checkIfSelected(mode, block, selection) + val background = block.parseThemeBackgroundColor() + val decorations = schema.toBlockViewDecoration(block) + + return when (content.type) { + Content.File.Type.IMAGE -> content.toPictureView(context, block.id, urlBuilder, indent, blockViewMode, isSelected, background, isPreviousBlockMedia, decorations, details, fieldParser) + Content.File.Type.VIDEO -> content.toVideoView(context, block.id, urlBuilder, indent, blockViewMode, isSelected, background, isPreviousBlockMedia, decorations, details, fieldParser) + else -> content.toFileView(context, block.id, urlBuilder, indent, blockViewMode, isSelected, background, isPreviousBlockMedia, decorations, details, fieldParser) + } } private fun title( @@ -1511,7 +1442,7 @@ class DefaultBlockViewRenderer @Inject constructor( val layoutCode = details.details[root.id]?.layout?.toInt() - val layout = ObjectType.Layout.values().find { + val layout = ObjectType.Layout.entries.find { it.code == layoutCode } ?: ObjectType.Layout.BASIC @@ -1572,18 +1503,32 @@ class DefaultBlockViewRenderer @Inject constructor( ) } ObjectType.Layout.FILE, - ObjectType.Layout.IMAGE, ObjectType.Layout.BOOKMARK, ObjectType.Layout.VIDEO, ObjectType.Layout.AUDIO, ObjectType.Layout.PDF -> { + val objFile = ObjectWrapper.Basic(details.details[root.id]?.map.orEmpty()) + BlockView.Title.File( + mode = blockMode, + id = block.id, + text = content.text, + isFocused = resolveIsFocused(focus, block), + cursor = cursor, + coverColor = coverContainer.coverColor, + coverImage = coverContainer.coverImage, + coverGradient = coverContainer.coverGradient, + background = block.parseThemeBackgroundColor(), + color = block.textColor(), + icon = objFile.objectIcon(builder = urlBuilder) + ) + } + ObjectType.Layout.IMAGE -> { BlockView.Title.Basic( mode = blockMode, id = block.id, text = content.text, - emoji = details.details[root.id]?.iconEmoji?.takeIf { it.isNotBlank() }, image = details.details[root.id]?.iconImage?.let { image -> - if (image.isNotBlank() && layout != ObjectType.Layout.BOOKMARK) + if (image.isNotBlank()) urlBuilder.thumbnail(image) else null diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/selection/TableCellExt.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/selection/TableCellExt.kt index 16a70566b7..403a17e199 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/selection/TableCellExt.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/selection/TableCellExt.kt @@ -283,6 +283,7 @@ fun List.toggleTableMode( is BlockView.Title.Basic -> view.copy( mode = cellsMode ) + is BlockView.Title.File -> view is BlockView.Title.Profile -> view.copy( mode = cellsMode ) @@ -352,6 +353,8 @@ fun List.toggleTableMode( is BlockView.DataView.EmptyData -> view.copy(isSelected = false) is BlockView.DataView.EmptySource -> view.copy(isSelected = false) is BlockView.DataView.Deleted -> view.copy(isSelected = false) + is BlockView.ButtonOpenFile.ImageButton -> view + is BlockView.ButtonOpenFile.FileButton -> view } } } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/extension/FileUrlExt.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/extension/FileUrlExt.kt index e26e9df5a3..bff85bb254 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/extension/FileUrlExt.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/extension/FileUrlExt.kt @@ -1,10 +1,14 @@ package com.anytypeio.anytype.presentation.extension import com.anytypeio.anytype.core_models.Block +import com.anytypeio.anytype.core_models.Block.Content import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.ObjectType import com.anytypeio.anytype.core_models.Url import com.anytypeio.anytype.domain.misc.UrlBuilder +import com.anytypeio.anytype.domain.primitives.FieldParser +import com.anytypeio.anytype.presentation.editor.editor.Orchestrator +import timber.log.Timber fun UrlBuilder.getUrlForFileBlock( fileBlock: Block?, @@ -66,3 +70,52 @@ fun UrlBuilder.getUrlBasedOnFileLayout( else -> null } } + + +data class FileDetails( + val content: Block.Content.File, + val targetObjectId: String, + val fileName: String, +) + +/** + * Attempts to retrieve and validate the file details for a given block [blockId]. + * Returns [FileDetails] if successful, or `null` if something went wrong. + */ +fun List.getFileDetailsForBlock( + blockId: String, + orchestrator: Orchestrator, + fieldParser: FieldParser +): FileDetails? { + + val block = firstOrNull { it.id == blockId } ?: run { + Timber.e("No block found with id $blockId") + return null + } + + val content = block.content + if (content !is Content.File || content.state != Content.File.State.DONE) { + Timber.e("Block content is not a file or is not in the DONE state; cannot proceed.") + return null + } + + val targetObjectId = content.targetObjectId + if (targetObjectId.isEmpty()) { + Timber.e("Target object ID is empty; cannot proceed with file sharing.") + return null + } + + val fileObject = orchestrator.stores.details.getAsObject(target = targetObjectId) + if (fileObject == null) { + Timber.e("Object with id $targetObjectId not found.") + return null + } + + val fileName = fieldParser.getObjectName(fileObject) + + return FileDetails( + content = content, + targetObjectId = targetObjectId, + fileName = fileName, + ) +} diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/history/VersionHistoryViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/history/VersionHistoryViewModel.kt index 5f041d1f6f..c05a74a52c 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/history/VersionHistoryViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/history/VersionHistoryViewModel.kt @@ -529,6 +529,7 @@ class VersionHistoryViewModel( defaultPayloadConsumer(payload) val root = event.blocks.first { it.id == vmParams.objectId } val blocks = event.blocks.asMap().render( + context = obj.id, mode = Mode.Read, root = root, focus = Editor.Focus.empty(), @@ -566,6 +567,7 @@ class VersionHistoryViewModel( } else { val root = event.blocks.first { it.id == vmParams.objectId } val blocks = event.blocks.asMap().render( + context = obj.id, mode = Mode.Read, root = root, focus = Editor.Focus.empty(), diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/mapper/MapperExtension.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/mapper/MapperExtension.kt index bfa7b84a40..f658bb3af0 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/mapper/MapperExtension.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/mapper/MapperExtension.kt @@ -9,6 +9,7 @@ import com.anytypeio.anytype.core_models.ObjectType import com.anytypeio.anytype.core_models.ObjectWrapper import com.anytypeio.anytype.core_models.RelationFormat import com.anytypeio.anytype.core_models.Relations +import com.anytypeio.anytype.core_models.SupportedLayouts import com.anytypeio.anytype.core_models.ThemeColor import com.anytypeio.anytype.domain.config.DebugSettings import com.anytypeio.anytype.domain.misc.UrlBuilder @@ -31,6 +32,7 @@ import com.anytypeio.anytype.presentation.templates.TemplateObjectTypeView import timber.log.Timber fun Block.Content.File.toPictureView( + context: Id, blockId: String, urlBuilder: UrlBuilder, indent: Int, @@ -39,7 +41,8 @@ fun Block.Content.File.toPictureView( background: ThemeColor, isPreviousBlockMedia: Boolean, decorations: List, - details: Block.Details = Block.Details() + details: Block.Details = Block.Details(), + fieldParser: FieldParser ): BlockView = when (state) { Block.Content.File.State.EMPTY -> BlockView.MediaPlaceholder.Picture( id = blockId, @@ -59,25 +62,32 @@ fun Block.Content.File.toPictureView( decorations = decorations ) Block.Content.File.State.DONE -> { + val url = urlBuilder.getUrlForFileContent(this) - val targetId = this.targetObjectId - val struct = details.details[targetId]?.map - if (url != null && targetId != null) { - val targetObject = ObjectWrapper.File(struct.orEmpty()) - BlockView.Media.Picture( - id = blockId, - targetObjectId = targetId, - url = url, - indent = indent, - mode = mode, - isSelected = isSelected, - background = background, - decorations = decorations, - size = targetObject.sizeInBytes?.toLong(), - name = targetObject.name, - mime = targetObject.fileMimeType - ) + val currentObject = ObjectWrapper.Basic(details.details[context]?.map.orEmpty()) + val targetObject = ObjectWrapper.Basic(details.details[targetObjectId]?.map.orEmpty()) + if (url != null && targetObject.isValid && targetObject.notDeletedNorArchived) { + if (currentObject.layout == ObjectType.Layout.IMAGE) { + BlockView.ButtonOpenFile.ImageButton( + id = blockId, + targetId = targetObjectId + ) + } else { + BlockView.Media.Picture( + id = blockId, + targetObjectId = targetObjectId, + url = url, + indent = indent, + mode = mode, + isSelected = isSelected, + background = background, + decorations = decorations, + size = targetObject.sizeInBytes?.toLong(), + name = fieldParser.getObjectName(targetObject), + mime = targetObject.fileMimeType + ) + } } else { Timber.w("Could not build picture view for block $blockId") BlockView.Error.Picture( @@ -99,10 +109,10 @@ fun Block.Content.File.toPictureView( decorations = decorations, name = name ) - else -> throw IllegalStateException("Unexpected state: $state") } fun Block.Content.File.toVideoView( + context: Id, blockId: Id, urlBuilder: UrlBuilder, indent: Int, @@ -111,7 +121,8 @@ fun Block.Content.File.toVideoView( background: ThemeColor, isPrevBlockMedia: Boolean, decorations: List, - details: Block.Details = Block.Details() + details: Block.Details = Block.Details(), + fieldParser: FieldParser ): BlockView = when (state) { Block.Content.File.State.EMPTY -> BlockView.MediaPlaceholder.Video( id = blockId, @@ -131,25 +142,32 @@ fun Block.Content.File.toVideoView( decorations = decorations ) Block.Content.File.State.DONE -> { + val url = urlBuilder.getUrlForFileContent(this) - val targetId = this.targetObjectId - val struct = details.details[targetId]?.map - if (url != null && targetId != null && !struct.isNullOrEmpty()) { - val targetObject = ObjectWrapper.File(struct) - BlockView.Media.Video( - id = blockId, - targetObjectId = targetId, - url = url, - indent = indent, - mode = mode, - isSelected = isSelected, - background = background, - decorations = decorations, - size = targetObject.sizeInBytes?.toLong(), - name = targetObject.name, - mime = targetObject.fileMimeType - ) + val currentObject = ObjectWrapper.Basic(details.details[context]?.map.orEmpty()) + val targetObject = ObjectWrapper.Basic(details.details[targetObjectId]?.map.orEmpty()) + if (url != null && targetObject.isValid && targetObject.notDeletedNorArchived) { + if (currentObject.layout == ObjectType.Layout.VIDEO) { + BlockView.ButtonOpenFile.FileButton( + id = blockId, + targetId = targetObjectId + ) + } else { + BlockView.Media.Video( + id = blockId, + targetObjectId = targetObjectId, + url = url, + indent = indent, + mode = mode, + isSelected = isSelected, + background = background, + decorations = decorations, + size = targetObject.sizeInBytes?.toLong(), + name = fieldParser.getObjectName(targetObject), + mime = targetObject.fileMimeType + ) + } } else { Timber.w("Could not build video view for block $blockId") BlockView.Error.Video( @@ -171,10 +189,10 @@ fun Block.Content.File.toVideoView( decorations = decorations, name = name ) - else -> throw IllegalStateException("Unexpected state: $state") } fun Block.Content.File.toFileView( + context: Id, blockId: String, urlBuilder: UrlBuilder, indent: Int, @@ -183,7 +201,8 @@ fun Block.Content.File.toFileView( background: ThemeColor, isPrevBlockMedia: Boolean, decorations: List, - details: Block.Details = Block.Details() + details: Block.Details = Block.Details(), + fieldParser: FieldParser ): BlockView = when (state) { Block.Content.File.State.EMPTY -> BlockView.MediaPlaceholder.File( id = blockId, @@ -203,24 +222,21 @@ fun Block.Content.File.toFileView( decorations = decorations ) Block.Content.File.State.DONE -> { + val url = urlBuilder.getUrlForFileContent(this) - val targetId = this.targetObjectId - val struct = details.details[targetId]?.map - if (url != null && targetId != null) { - if (struct.isNullOrEmpty()) { - BlockView.Upload.File( + val currentObject = ObjectWrapper.Basic(details.details[context]?.map.orEmpty()) + val targetObject = ObjectWrapper.Basic(details.details[targetObjectId]?.map.orEmpty()) + + if (url != null && targetObject.isValid && targetObject.notDeletedNorArchived) { + if (SupportedLayouts.fileLayouts.contains(currentObject.layout)) { + BlockView.ButtonOpenFile.FileButton( id = blockId, - indent = indent, - mode = mode, - isSelected = isSelected, - background = background, - decorations = decorations + targetId = targetObjectId ) } else { - val targetObject = ObjectWrapper.File(struct) BlockView.Media.File( id = blockId, - targetObjectId = targetId, + targetObjectId = targetObjectId, url = url, indent = indent, mode = mode, @@ -228,7 +244,7 @@ fun Block.Content.File.toFileView( background = background, decorations = decorations, size = targetObject.sizeInBytes?.toLong(), - name = targetObject.name, + name = fieldParser.getObjectName(targetObject), mime = targetObject.fileMimeType, fileExt = targetObject.fileExt ) @@ -254,7 +270,6 @@ fun Block.Content.File.toFileView( decorations = decorations, name = name ) - else -> throw IllegalStateException("Unexpected state: $state") } fun Block.Align.toView(): Alignment = when (this) { diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/templates/TemplateBlankViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/templates/TemplateBlankViewModel.kt index 1ce3ee21c8..9c96408b70 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/templates/TemplateBlankViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/templates/TemplateBlankViewModel.kt @@ -94,6 +94,7 @@ class TemplateBlankViewModel( viewModelScope.launch { state.value = page.asMap().render( + context = DEFAULT_TEMPLATE_ID_BLANK, mode = Editor.Mode.Read, root = page.first(), focus = com.anytypeio.anytype.domain.editor.Editor.Focus.empty(), diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/DefaultBlockViewRendererTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/DefaultBlockViewRendererTest.kt index 9259d1487f..318df39ecc 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/DefaultBlockViewRendererTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/DefaultBlockViewRendererTest.kt @@ -76,6 +76,7 @@ class DefaultBlockViewRendererTest { details: Block.Details, schema: NestedDecorationData = emptyList() ): List = blocks.render( + context = root.id, root = root, focus = focus, anchor = anchor, @@ -5754,24 +5755,106 @@ class DefaultBlockViewRendererTest { //endregion @Test - fun `should not render file block in case of file layout`() { + fun `should render file open block in case of file, pdf, audio or video layouts`(){ + val fileTypeExceptImage = listOf( + Block.Content.File.Type.FILE to Layout.FILE, + Block.Content.File.Type.PDF to Layout.PDF, + Block.Content.File.Type.VIDEO to Layout.VIDEO, + Block.Content.File.Type.AUDIO to Layout.AUDIO, + ) + + fileTypeExceptImage.forEach { (fileType, layout) -> + testFileLayout(type = fileType, layout = layout) + } + } + + private fun testFileLayout(type: Block.Content.File.Type, layout: Layout) { + + val currentObjectId = MockDataFactory.randomUuid() + + val file = StubFile(type = type, targetObjectId = currentObjectId) + + val paragraph = StubParagraph() + + val page = StubSmartBlock(id = currentObjectId, children = listOf(paragraph.id, file.id)) + + val details = mapOf( + page.id to Block.Fields( + mapOf( + Relations.ID to currentObjectId, + Relations.NAME to "file-name", + Relations.LAYOUT to layout.code.toDouble() + ) + ) + ) + + val blocks = listOf(page, paragraph, file) + + val map = blocks.asMap() + + wrapper = BlockViewRenderWrapper( + blocks = map, + renderer = renderer + ) + + val result = runBlocking { + wrapper.render( + root = page, + anchor = page.id, + focus = Editor.Focus.empty(), + indent = 0, + details = Block.Details(details) + ) + } + + val expected = listOf( + BlockView.Text.Paragraph( + indent = 0, + isFocused = false, + id = paragraph.id, + marks = emptyList(), + background = paragraph.parseThemeBackgroundColor(), + text = paragraph.content().text, + decorations = listOf( + BlockView.Decoration( + style = BlockView.Decoration.Style.None, + background = paragraph.parseThemeBackgroundColor() + ) + ), + ), + BlockView.ButtonOpenFile.FileButton( + id = file.id, + targetId = currentObjectId + ) + ) + + assertEquals(expected = expected, actual = result) + } + + @Test + fun `should render image open block in case of image layout`() { + + val currentObjectId = MockDataFactory.randomUuid() val file = StubFile( - backgroundColor = null, - state = Block.Content.File.State.DONE, - type = Block.Content.File.Type.FILE + type = Block.Content.File.Type.IMAGE, + targetObjectId = currentObjectId, + state = Block.Content.File.State.DONE ) val paragraph = StubParagraph() - val page = StubSmartBlock(children = listOf(paragraph.id, file.id)) + val page = StubSmartBlock(id = currentObjectId, children = listOf(paragraph.id, file.id)) - val details = mapOf(page.id to Block.Fields( - mapOf( - Relations.NAME to "file-name", - Relations.LAYOUT to Layout.FILE.code.toDouble() + val details = mapOf( + page.id to Block.Fields( + mapOf( + Relations.ID to currentObjectId, + Relations.NAME to "image-name", + Relations.LAYOUT to ObjectType.Layout.IMAGE.code.toDouble() + ) ) - )) + ) val blocks = listOf(page, paragraph, file) @@ -5806,6 +5889,10 @@ class DefaultBlockViewRendererTest { background = paragraph.parseThemeBackgroundColor() ) ), + ), + BlockView.ButtonOpenFile.ImageButton( + id = file.id, + targetId = currentObjectId ) ) diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/EditorViewModelTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/EditorViewModelTest.kt index c8af0443ea..b4901821c7 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/EditorViewModelTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/EditorViewModelTest.kt @@ -1,6 +1,7 @@ package com.anytypeio.anytype.presentation.editor +import android.R import android.net.Uri import android.os.Build import androidx.arch.core.executor.testing.InstantTaskExecutorRule @@ -9,11 +10,15 @@ import com.anytypeio.anytype.core_models.Block import com.anytypeio.anytype.core_models.Event import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.NetworkModeConfig +import com.anytypeio.anytype.core_models.ObjectType +import com.anytypeio.anytype.core_models.ObjectTypeIds import com.anytypeio.anytype.core_models.Payload import com.anytypeio.anytype.core_models.Position import com.anytypeio.anytype.core_models.Relation +import com.anytypeio.anytype.core_models.Relations import com.anytypeio.anytype.core_models.StubFile import com.anytypeio.anytype.core_models.StubNumbered +import com.anytypeio.anytype.core_models.StubObject import com.anytypeio.anytype.core_models.StubParagraph import com.anytypeio.anytype.core_models.ThemeColor import com.anytypeio.anytype.core_models.ext.content @@ -2555,68 +2560,24 @@ open class EditorViewModelTest { } @Test - fun `should start sharing a file`() { + fun `should start downloading file`() { val root = MockDataFactory.randomUuid() - val file = MockBlockFactory.makeFileBlock() + val targetObjectId = MockDataFactory.randomUuid() + val file = StubFile( + targetObjectId = targetObjectId, + type = Block.Content.File.Type.FILE + ) val title = MockBlockFactory.makeTitleBlock() - val page = listOf( - Block( - id = root, - fields = Block.Fields(emptyMap()), - content = Block.Content.Smart, - children = listOf(title.id, file.id) - ), - title, - file + val targetObject = StubObject( + id = targetObjectId, + name = "file1", + layout = ObjectType.Layout.FILE.code.toDouble(), + fileExt = ".pdf" ) - val flow: Flow> = flow { - delay(100) - emit( - listOf( - Event.Command.ShowObject( - root = root, - blocks = page, - context = root - ) - ) - ) - } - - stubObserveEvents(flow) - stubOpenPage() - givenViewModel(builder) - - givenSharedFile() - - vm.onStart(id = root, space = defaultSpace) - - coroutineTestRule.advanceTime(100) - - // TESTING - - vm.startSharingFile(id = file.id) - - runTest { - verify(documentFileShareDownloader, times(1)).async( - params = eq( - MiddlewareShareDownloader.Params( - name = file.content().name.orEmpty(), - objectId = file.content().targetObjectId.orEmpty(), - ) - ) - ) - } - } - - @Test - fun `should start downloading file`() { - - val root = MockDataFactory.randomUuid() - val file = MockBlockFactory.makeFileBlock() - val title = MockBlockFactory.makeTitleBlock() + val objectDetails = Block.Fields(targetObject.map) val page = listOf( Block( @@ -2636,12 +2597,19 @@ open class EditorViewModelTest { Event.Command.ShowObject( root = root, blocks = page, - context = root + context = root, + details = Block.Details(mapOf( + targetObjectId to objectDetails + )) ) ) ) } + fieldParser.stub { + on { getObjectName(targetObject) } doReturn targetObject.name!! + } + stubObserveEvents(flow) stubOpenPage() givenViewModel(builder) @@ -2654,16 +2622,14 @@ open class EditorViewModelTest { // TESTING - vm.startDownloadingFileFromBlock(blockId = file.id) + vm.startDownloadingFileFromBlock(id = file.id) runBlockingTest { verify(downloadFile, times(1)).invoke( params = eq( DownloadFile.Params( - name = file.content().name.orEmpty(), - url = builder.file( - path = file.content().targetObjectId!! - ) + name = targetObject.name!!, + url = builder.file(path = targetObjectId) ) ) ) @@ -3423,17 +3389,11 @@ open class EditorViewModelTest { } @Test - fun `open select picture - when error in edit mode`() { + fun `open select picture - when error in edit mode`() = runTest { - val picture = Block( - id = MockDataFactory.randomUuid(), - fields = Block.Fields(emptyMap()), - content = Block.Content.File( - targetObjectId = MockDataFactory.randomString(), - type = Block.Content.File.Type.IMAGE, - state = Block.Content.File.State.ERROR - ), - children = emptyList() + val picture = StubFile( + state = Block.Content.File.State.ERROR, + type = Block.Content.File.Type.IMAGE ) val page = listOf( @@ -3457,10 +3417,10 @@ open class EditorViewModelTest { ) givenViewModel() - + advanceUntilIdle() vm.onStart(id = root, space = defaultSpace) - + advanceUntilIdle() val expected = listOf( BlockView.Title.Basic( @@ -3477,7 +3437,8 @@ open class EditorViewModelTest { background = ThemeColor.DEFAULT, style = BlockView.Decoration.Style.Card ) - ) + ), + name = picture.content.asFile().name ) ) @@ -3500,15 +3461,9 @@ open class EditorViewModelTest { @Test fun `open select video - when error in edit mode`() { - val video = Block( - id = MockDataFactory.randomUuid(), - fields = Block.Fields(emptyMap()), - content = Block.Content.File( - targetObjectId = MockDataFactory.randomString(), - type = Block.Content.File.Type.VIDEO, - state = Block.Content.File.State.ERROR - ), - children = emptyList() + val video = StubFile( + state = Block.Content.File.State.ERROR, + type = Block.Content.File.Type.VIDEO ) val page = listOf( @@ -3552,7 +3507,8 @@ open class EditorViewModelTest { background = ThemeColor.DEFAULT, style = BlockView.Decoration.Style.Card ) - ) + ), + name = video.content.asFile().name ) ) diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorErrorMessageTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorErrorMessageTest.kt index 7bbcd44983..480e053843 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorErrorMessageTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorErrorMessageTest.kt @@ -2,6 +2,10 @@ package com.anytypeio.anytype.presentation.editor.editor import androidx.arch.core.executor.testing.InstantTaskExecutorRule import com.anytypeio.anytype.core_models.Block +import com.anytypeio.anytype.core_models.ObjectType +import com.anytypeio.anytype.core_models.Relations +import com.anytypeio.anytype.core_models.StubFile +import com.anytypeio.anytype.core_models.StubObject import com.anytypeio.anytype.domain.base.Either import com.anytypeio.anytype.presentation.util.DefaultCoroutineTestRule import com.anytypeio.anytype.test_utils.MockDataFactory @@ -38,15 +42,26 @@ class EditorErrorMessageTest : EditorPresentationTestSetup() { val consumed = mutableListOf() - val file = Block( - id = MockDataFactory.randomUuid(), - content = Block.Content.File( - targetObjectId = MockDataFactory.randomUuid(), - type = Block.Content.File.Type.FILE, - state = Block.Content.File.State.DONE - ), - fields = Block.Fields.empty(), - children = emptyList() + val fileObjectId = MockDataFactory.randomUuid() + + val fileBlock = StubFile( + type = Block.Content.File.Type.FILE, + state = Block.Content.File.State.DONE, + targetObjectId = fileObjectId + ) + + val details = Block.Details( + mapOf( + fileObjectId to Block.Fields( + mapOf( + Relations.ID to fileObjectId, + Relations.NAME to "file object", + Relations.SIZE_IN_BYTES to 10000.0, + Relations.FILE_MIME_TYPE to "pdf", + Relations.LAYOUT to ObjectType.Layout.FILE.code.toDouble() + ) + ) + ) ) val doc = listOf( @@ -54,17 +69,19 @@ class EditorErrorMessageTest : EditorPresentationTestSetup() { id = root, fields = Block.Fields(emptyMap()), content = Block.Content.Smart, - children = listOf(file.id) + children = listOf(fileBlock.id) ), - file + fileBlock ) - stubOpenDocument(doc) + stubOpenDocument(doc, details) stubInterceptEvents() stubDownloadFile() val vm = buildViewModel() + advanceUntilIdle() + vm.onStart(id = root, space = defaultSpace) advanceUntilIdle() @@ -73,7 +90,7 @@ class EditorErrorMessageTest : EditorPresentationTestSetup() { // Launching operation that triggers a toast - vm.startDownloadingFileFromBlock(blockId = file.id) + vm.startDownloadingFileFromBlock(id = fileBlock.id) advanceUntilIdle() diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorLockPageTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorLockPageTest.kt index 1513c379c2..784dda8567 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorLockPageTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorLockPageTest.kt @@ -1,11 +1,11 @@ package com.anytypeio.anytype.presentation.editor.editor -import android.R import androidx.arch.core.executor.testing.InstantTaskExecutorRule import com.anytypeio.anytype.core_models.Block import com.anytypeio.anytype.core_models.ObjectType import com.anytypeio.anytype.core_models.Relations import com.anytypeio.anytype.core_models.StubBookmark +import com.anytypeio.anytype.core_models.StubFile import com.anytypeio.anytype.core_models.StubTitle import com.anytypeio.anytype.core_models.ThemeColor import com.anytypeio.anytype.core_models.ext.content @@ -28,8 +28,6 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.mockito.MockitoAnnotations -import org.mockito.kotlin.doReturn -import org.mockito.kotlin.stub import org.mockito.kotlin.whenever class EditorLockPageTest : EditorPresentationTestSetup() { @@ -585,15 +583,11 @@ class EditorLockPageTest : EditorPresentationTestSetup() { val fileBlockId = MockDataFactory.randomUuid() val targetObjectId = MockDataFactory.randomUuid() - val file = Block( + val file = StubFile( id = fileBlockId, - fields = Block.Fields(emptyMap()), - content = Block.Content.File( - targetObjectId = targetObjectId, - type = Block.Content.File.Type.FILE, - state = Block.Content.File.State.DONE - ), - children = emptyList() + type = Block.Content.File.Type.FILE, + state = Block.Content.File.State.DONE, + targetObjectId = targetObjectId ) val page = listOf( @@ -680,8 +674,7 @@ class EditorLockPageTest : EditorPresentationTestSetup() { testObserver.assertValue { value -> value is EventWrapper && value.peekContent() == Command.OpenFileByDefaultApp( - id = fileBlockId, - uri = builder.file(targetObjectId) + id = fileBlockId ) } } @@ -697,15 +690,10 @@ class EditorLockPageTest : EditorPresentationTestSetup() { val fileName = "image.png" val fileSize = 1000.0 - val picture = Block( - id = fileBlockId, - fields = Block.Fields(emptyMap()), - content = Block.Content.File( - targetObjectId = targetObjectId, - type = Block.Content.File.Type.IMAGE, - state = Block.Content.File.State.DONE - ), - children = emptyList() + val picture = StubFile( + type = Block.Content.File.Type.IMAGE, + state = Block.Content.File.State.DONE, + targetObjectId = targetObjectId ) val page = listOf( diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorMultiSelectModeTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorMultiSelectModeTest.kt index 9a42e8b278..3dea8cec04 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorMultiSelectModeTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorMultiSelectModeTest.kt @@ -3,6 +3,7 @@ package com.anytypeio.anytype.presentation.editor.editor import android.os.Build import androidx.arch.core.executor.testing.InstantTaskExecutorRule import com.anytypeio.anytype.core_models.Block +import com.anytypeio.anytype.core_models.StubFile import com.anytypeio.anytype.core_models.StubHeader import com.anytypeio.anytype.core_models.StubLayoutColumns import com.anytypeio.anytype.core_models.StubLayoutRows @@ -1081,14 +1082,11 @@ class EditorMultiSelectModeTest : EditorPresentationTestSetup() { ) val backgroundC = null - val c = Block( - id = MockDataFactory.randomUuid(), - fields = Block.Fields.empty(), - children = emptyList(), - content = Block.Content.File( - state = Block.Content.File.State.EMPTY - ), - backgroundColor = backgroundC + + + val c = StubFile( + backgroundColor = backgroundC, + state = Block.Content.File.State.EMPTY ) val page = Block( diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/styling/StyleToolbarExtKtTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/styling/StyleToolbarExtKtTest.kt index a399c4856a..d9c647cf4e 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/styling/StyleToolbarExtKtTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/styling/StyleToolbarExtKtTest.kt @@ -2,6 +2,7 @@ package com.anytypeio.anytype.presentation.editor.editor.styling import android.os.Build import com.anytypeio.anytype.core_models.Block +import com.anytypeio.anytype.core_models.StubFile import com.anytypeio.anytype.core_models.TextStyle import com.anytypeio.anytype.core_models.ThemeColor import com.anytypeio.anytype.test_utils.MockDataFactory @@ -140,12 +141,9 @@ class StyleToolbarExtKtTest { children = emptyList() ) - val given2 = Block( + val given2 = StubFile( id = child, - fields = Block.Fields(emptyMap()), - content = Block.Content.File(), - backgroundColor = backgroundTeal, - children = emptyList() + backgroundColor = backgroundTeal ) val given3 = Block( @@ -373,12 +371,9 @@ class StyleToolbarExtKtTest { children = emptyList() ) - val given3 = Block( + val given3 = StubFile( id = child, - fields = Block.Fields(emptyMap()), - content = Block.Content.File(), - backgroundColor = ThemeColor.LIME.code, - children = emptyList() + backgroundColor = ThemeColor.LIME.code ) val result = diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/table/TableBlockRendererTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/table/TableBlockRendererTest.kt index 2f7e61e375..ccbacf5e19 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/table/TableBlockRendererTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/table/TableBlockRendererTest.kt @@ -55,6 +55,7 @@ class TableBlockRendererTest { indent: Int, details: Block.Details ): List = blocks.render( + context = root.id, root = root, anchor = anchor, focus = focus, diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/mapper/MapperExtensionKtTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/mapper/MapperExtensionKtTest.kt index 196b680680..8531eb33a3 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/mapper/MapperExtensionKtTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/mapper/MapperExtensionKtTest.kt @@ -2,11 +2,20 @@ package com.anytypeio.anytype.presentation.mapper import com.anytypeio.anytype.core_models.Block import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.ObjectType import com.anytypeio.anytype.core_models.Relations +import com.anytypeio.anytype.core_models.StubFile +import com.anytypeio.anytype.core_models.StubObject import com.anytypeio.anytype.domain.config.Gateway import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.presentation.editor.editor.Markup import com.anytypeio.anytype.core_models.ThemeColor +import com.anytypeio.anytype.domain.debugging.Logger +import com.anytypeio.anytype.domain.misc.DateProvider +import com.anytypeio.anytype.domain.objects.GetDateObjectByTimestamp +import com.anytypeio.anytype.domain.primitives.FieldParser +import com.anytypeio.anytype.domain.primitives.FieldParserImpl +import com.anytypeio.anytype.domain.resources.StringResourceProvider import com.anytypeio.anytype.presentation.editor.editor.model.BlockView import com.anytypeio.anytype.test_utils.MockDataFactory import org.junit.Before @@ -23,10 +32,26 @@ class MapperExtensionKtTest { private val urlBuilder: UrlBuilder get() = UrlBuilder(gateway) private val targetObjectId : Id = "647tyhfgehf7ru" + private val objectId : Id = MockDataFactory.randomUuid() + + lateinit var fieldParser: FieldParser + + @Mock + lateinit var dateProvider: DateProvider + + @Mock + lateinit var logger: Logger + + @Mock + lateinit var getDateObjectByTimestamp: GetDateObjectByTimestamp + + @Mock + lateinit var stringResourceProvider: StringResourceProvider @Before fun before() { MockitoAnnotations.openMocks(this) + fieldParser = FieldParserImpl(dateProvider, logger, getDateObjectByTimestamp, stringResourceProvider) } @Test @@ -44,25 +69,28 @@ class MapperExtensionKtTest { val details = Block.Details( mapOf( + objectId to Block.Fields( + StubObject( + layout = ObjectType.Layout.BASIC.code.toDouble() + ).map + ), targetObjectId to Block.Fields( mapOf( + Relations.ID to targetObjectId, Relations.NAME to name, Relations.SIZE_IN_BYTES to 10000.0, Relations.FILE_MIME_TYPE to mime, + Relations.LAYOUT to ObjectType.Layout.FILE.code.toDouble() ) ) ) ) - val block = Block.Content.File( - name = name, - size = 10000L, - mime = mime, - targetObjectId = targetObjectId, + val block = StubFile( state = state, - type = type - - ) + type = type, + targetObjectId = targetObjectId + ).content as Block.Content.File val expected = BlockView.Media.File( id = id, @@ -75,7 +103,7 @@ class MapperExtensionKtTest { indent = indent, decorations = emptyList() ) - val actual = block.toFileView(id, urlBuilder, indent, mode, false, ThemeColor.DEFAULT, false, emptyList(), details) + val actual = block.toFileView(objectId, id, urlBuilder, indent, mode, false, ThemeColor.DEFAULT, false, emptyList(), details, fieldParser) assertEquals(expected, actual) } @@ -91,15 +119,15 @@ class MapperExtensionKtTest { val type = Block.Content.File.Type.FILE val mode = BlockView.Mode.EDIT - val block = Block.Content.File( + val block = StubFile( state = state, type = type, targetObjectId = targetObjectId - ) + ).content as Block.Content.File val expected = BlockView.MediaPlaceholder.File(id = id, indent = indent, isPreviousBlockMedia = false) - val actual = block.toFileView(id, urlBuilder, indent, mode, false, ThemeColor.DEFAULT, false, emptyList()) + val actual = block.toFileView(objectId, id, urlBuilder, indent, mode, false, ThemeColor.DEFAULT, false, emptyList(), Block.Details(), fieldParser) assertEquals(expected, actual) } @@ -107,6 +135,8 @@ class MapperExtensionKtTest { @Test fun `should return error file block view`() { + val rootBlockId = MockDataFactory.randomUuid() + val id = MockDataFactory.randomUuid() val indent = MockDataFactory.randomInt() @@ -115,14 +145,14 @@ class MapperExtensionKtTest { val type = Block.Content.File.Type.FILE val mode = BlockView.Mode.EDIT - val block = Block.Content.File( + val block = StubFile( state = state, type = type, targetObjectId = targetObjectId - ) + ).content as Block.Content.File - val expected = BlockView.Error.File(id = id, indent = indent, decorations = emptyList()) - val actual = block.toFileView(id, urlBuilder, indent, mode, false, ThemeColor.DEFAULT, false, emptyList()) + val expected = BlockView.Error.File(id = id, indent = indent, decorations = emptyList(), name = block.name) + val actual = block.toFileView(objectId, id, urlBuilder, indent, mode, false, ThemeColor.DEFAULT, false, emptyList(), Block.Details(), fieldParser) assertEquals(expected, actual) } @@ -138,14 +168,14 @@ class MapperExtensionKtTest { val type = Block.Content.File.Type.FILE val mode = BlockView.Mode.EDIT - val block = Block.Content.File( + val block = StubFile( state = state, type = type, targetObjectId = targetObjectId - ) + ).content as Block.Content.File val expected = BlockView.Upload.File(id = id, indent = indent) - val actual = block.toFileView(id, urlBuilder, indent, mode, false, ThemeColor.DEFAULT, false, emptyList()) + val actual = block.toFileView(objectId, id, urlBuilder, indent, mode, false, ThemeColor.DEFAULT, false, emptyList(), Block.Details(), fieldParser) assertEquals(expected, actual) } @@ -165,17 +195,17 @@ class MapperExtensionKtTest { val type = Block.Content.File.Type.IMAGE val mode = BlockView.Mode.EDIT - val block = Block.Content.File( - targetObjectId = targetObjectId, + val block = StubFile( state = state, - type = type - - ) + type = type, + targetObjectId = targetObjectId + ).content as Block.Content.File val details = Block.Details( mapOf( targetObjectId to Block.Fields( mapOf( + Relations.ID to targetObjectId, Relations.NAME to name, Relations.SIZE_IN_BYTES to size, Relations.FILE_MIME_TYPE to mime, @@ -195,7 +225,7 @@ class MapperExtensionKtTest { decorations = emptyList() ) - val actual = block.toPictureView(id, urlBuilder, indent, mode, false, ThemeColor.DEFAULT, false, emptyList(), details) + val actual = block.toPictureView(objectId, id, urlBuilder, indent, mode, false, ThemeColor.DEFAULT, false, emptyList(), details, fieldParser) assertEquals(expected, actual) } @@ -211,18 +241,18 @@ class MapperExtensionKtTest { val type = Block.Content.File.Type.IMAGE val mode = BlockView.Mode.EDIT - val block = Block.Content.File( + val block = StubFile( state = state, type = type, targetObjectId = targetObjectId - ) + ).content as Block.Content.File val expected = BlockView.MediaPlaceholder.Picture( id = id, indent = indent, isPreviousBlockMedia = false ) - val actual = block.toPictureView(id, urlBuilder, indent, mode, false, ThemeColor.DEFAULT, false, emptyList()) + val actual = block.toPictureView(objectId, id, urlBuilder, indent, mode, false, ThemeColor.DEFAULT, false, emptyList(), Block.Details(), fieldParser) assertEquals(expected, actual) } @@ -238,19 +268,20 @@ class MapperExtensionKtTest { val type = Block.Content.File.Type.IMAGE val mode = BlockView.Mode.EDIT - val block = Block.Content.File( + val block = StubFile( state = state, type = type, targetObjectId = targetObjectId - ) + ).content as Block.Content.File val expected = BlockView.Error.Picture( id = id, indent = indent, - decorations = emptyList() + decorations = emptyList(), + name = block.name ) - val actual = block.toPictureView(id, urlBuilder, indent, mode, false, ThemeColor.DEFAULT, false, emptyList()) + val actual = block.toPictureView(objectId, id, urlBuilder, indent, mode, false, ThemeColor.DEFAULT, false, emptyList(), Block.Details(), fieldParser) assertEquals(expected, actual) } @@ -266,14 +297,14 @@ class MapperExtensionKtTest { val type = Block.Content.File.Type.IMAGE val mode = BlockView.Mode.EDIT - val block = Block.Content.File( + val block = StubFile( state = state, type = type, targetObjectId = targetObjectId - ) + ).content as Block.Content.File val expected = BlockView.Upload.Picture(id = id, indent = indent) - val actual = block.toPictureView(id, urlBuilder, indent, mode, false, ThemeColor.DEFAULT, false, emptyList()) + val actual = block.toPictureView(objectId, id, urlBuilder, indent, mode, false, ThemeColor.DEFAULT, false, emptyList(), Block.Details(), fieldParser) assertEquals(expected, actual) } @@ -293,16 +324,17 @@ class MapperExtensionKtTest { val type = Block.Content.File.Type.VIDEO val mode = BlockView.Mode.EDIT - val block = Block.Content.File( - targetObjectId = targetObjectId, + val block = StubFile( state = state, - type = type - ) + type = type, + targetObjectId = targetObjectId + ).content as Block.Content.File val details = Block.Details( mapOf( targetObjectId to Block.Fields( mapOf( + Relations.ID to targetObjectId, Relations.NAME to name, Relations.SIZE_IN_BYTES to 10000.0, Relations.FILE_MIME_TYPE to mime, @@ -322,7 +354,7 @@ class MapperExtensionKtTest { decorations = emptyList() ) - val actual = block.toVideoView(id, urlBuilder, indent, mode, false, ThemeColor.DEFAULT, false, emptyList(), details) + val actual = block.toVideoView(objectId, id, urlBuilder, indent, mode, false, ThemeColor.DEFAULT, false, emptyList(), details, fieldParser) assertEquals(expected, actual) } @@ -338,11 +370,11 @@ class MapperExtensionKtTest { val type = Block.Content.File.Type.VIDEO val mode = BlockView.Mode.EDIT - val block = Block.Content.File( - targetObjectId = null, + val block = StubFile( state = state, - type = type - ) + type = type, + targetObjectId = targetObjectId + ).content as Block.Content.File val expected = BlockView.Error.Video( id = id, @@ -350,7 +382,7 @@ class MapperExtensionKtTest { decorations = emptyList() ) - val actual = block.toVideoView(id, urlBuilder, indent, mode, false, ThemeColor.DEFAULT, false, emptyList()) + val actual = block.toVideoView(objectId, id, urlBuilder, indent, mode, false, ThemeColor.DEFAULT, false, emptyList(), Block.Details(), fieldParser) assertEquals(expected, actual) } @@ -366,11 +398,11 @@ class MapperExtensionKtTest { val type = Block.Content.File.Type.IMAGE val mode = BlockView.Mode.EDIT - val block = Block.Content.File( - targetObjectId = null, + val block = StubFile( state = state, - type = type - ) + type = type, + targetObjectId = "" + ).content as Block.Content.File val expected = BlockView.Error.Picture( id = id, @@ -378,7 +410,7 @@ class MapperExtensionKtTest { decorations = emptyList() ) - val actual = block.toPictureView(id, urlBuilder, indent, mode, false, ThemeColor.DEFAULT, false, emptyList()) + val actual = block.toPictureView(objectId, id, urlBuilder, indent, mode, false, ThemeColor.DEFAULT, false, emptyList(), Block.Details(), fieldParser) assertEquals(expected, actual) } @@ -394,11 +426,11 @@ class MapperExtensionKtTest { val type = Block.Content.File.Type.FILE val mode = BlockView.Mode.EDIT - val block = Block.Content.File( - targetObjectId = null, + val block = StubFile( state = state, - type = type - ) + type = type, + targetObjectId = "" + ).content as Block.Content.File val expected = BlockView.Error.File( id = id, @@ -406,7 +438,7 @@ class MapperExtensionKtTest { decorations = emptyList() ) - val actual = block.toFileView(id, urlBuilder, indent, mode, false, ThemeColor.DEFAULT, false, emptyList()) + val actual = block.toFileView(objectId, id, urlBuilder, indent, mode, false, ThemeColor.DEFAULT, false, emptyList(), Block.Details(), fieldParser) assertEquals(expected, actual) } @@ -422,11 +454,11 @@ class MapperExtensionKtTest { val type = Block.Content.File.Type.PDF val mode = BlockView.Mode.EDIT - val block = Block.Content.File( - targetObjectId = null, + val block = StubFile( state = state, - type = type - ) + type = type, + targetObjectId = "" + ).content as Block.Content.File val expected = BlockView.Error.File( id = id, @@ -434,7 +466,7 @@ class MapperExtensionKtTest { decorations = emptyList() ) - val actual = block.toFileView(id, urlBuilder, indent, mode, false, ThemeColor.DEFAULT, false, emptyList()) + val actual = block.toFileView(objectId, id, urlBuilder, indent, mode, false, ThemeColor.DEFAULT, false, emptyList(), Block.Details(), fieldParser) assertEquals(expected, actual) } @@ -450,14 +482,11 @@ class MapperExtensionKtTest { val type = Block.Content.File.Type.VIDEO val mode = BlockView.Mode.EDIT - val block = Block.Content.File( - name = null, - size = null, - mime = null, - targetObjectId = null, + val block = StubFile( state = state, - type = type - ) + type = type, + targetObjectId = "" + ).content as Block.Content.File val expected = BlockView.MediaPlaceholder.Video( id = id, @@ -465,7 +494,7 @@ class MapperExtensionKtTest { isPreviousBlockMedia = false ) - val actual = block.toVideoView(id, urlBuilder, indent, mode, false, ThemeColor.DEFAULT, false, emptyList()) + val actual = block.toVideoView(objectId, id, urlBuilder, indent, mode, false, ThemeColor.DEFAULT, false, emptyList(), Block.Details(), fieldParser) assertEquals(expected, actual) } @@ -481,21 +510,18 @@ class MapperExtensionKtTest { val type = Block.Content.File.Type.VIDEO val mode = BlockView.Mode.EDIT - val block = Block.Content.File( - name = null, - size = null, - mime = null, - targetObjectId = null, + val block = StubFile( state = state, - type = type - ) + type = type, + targetObjectId = "" + ).content as Block.Content.File val expected = BlockView.Upload.Video( id = id, indent = indent ) - val actual = block.toVideoView(id, urlBuilder, indent, mode, false, ThemeColor.DEFAULT, false, emptyList()) + val actual = block.toVideoView(objectId, id, urlBuilder, indent, mode, false, ThemeColor.DEFAULT, false, emptyList(), Block.Details(), fieldParser) assertEquals(expected, actual) } @@ -511,48 +537,24 @@ class MapperExtensionKtTest { val type = Block.Content.File.Type.VIDEO val mode = BlockView.Mode.EDIT - val block = Block.Content.File( - name = null, - size = null, - mime = null, - targetObjectId = null, + val block = StubFile( state = state, - type = type - ) + type = type, + targetObjectId = "" + ).content as Block.Content.File val expected = BlockView.Error.Video( id = id, indent = indent, - decorations = emptyList() + decorations = emptyList(), + name = block.name ) - val actual = block.toVideoView(id, urlBuilder, indent, mode, false, ThemeColor.DEFAULT, false, emptyList()) + val actual = block.toVideoView(objectId, id, urlBuilder, indent, mode, false, ThemeColor.DEFAULT, false, emptyList(), Block.Details(), fieldParser) assertEquals(expected, actual) } - @Test(expected = IllegalStateException::class) - fun `should throw exceptions when state not set`() { - - val id = MockDataFactory.randomUuid() - - val indent = MockDataFactory.randomInt() - - val type = Block.Content.File.Type.VIDEO - val mode = BlockView.Mode.EDIT - - val block = Block.Content.File( - name = null, - size = null, - mime = null, - targetObjectId = null, - state = null, - type = type - ) - - block.toVideoView(id, urlBuilder, indent, mode, false, ThemeColor.DEFAULT, false, emptyList()) - } - @Test fun `should not return mark when range from is equal text length`() { // SETUP diff --git a/test/core-models-stub/src/main/java/com/anytypeio/anytype/core_models/Block.kt b/test/core-models-stub/src/main/java/com/anytypeio/anytype/core_models/Block.kt index 2b16bf3bfd..f9f50eec4b 100644 --- a/test/core-models-stub/src/main/java/com/anytypeio/anytype/core_models/Block.kt +++ b/test/core-models-stub/src/main/java/com/anytypeio/anytype/core_models/Block.kt @@ -94,9 +94,11 @@ fun StubFile( backgroundColor: String? = null, targetObjectId: Id = MockDataFactory.randomString(), name: String = MockDataFactory.randomString(), + mime: String = MockDataFactory.randomString(), size: Long = MockDataFactory.randomLong(), - type: Block.Content.File.Type? = null, - state: Block.Content.File.State? = null, + type: Block.Content.File.Type = Block.Content.File.Type.FILE, + state: Block.Content.File.State = Block.Content.File.State.DONE, + addedAt: Long = MockDataFactory.randomLong() ) : Block = Block( id = id, children = children, @@ -107,7 +109,9 @@ fun StubFile( name = name, targetObjectId = targetObjectId, type = type, - state = state + state = state, + mime = mime, + addedAt = addedAt ) ) diff --git a/test/core-models-stub/src/main/java/com/anytypeio/anytype/core_models/Object.kt b/test/core-models-stub/src/main/java/com/anytypeio/anytype/core_models/Object.kt index a89ac80856..a4feed51cc 100644 --- a/test/core-models-stub/src/main/java/com/anytypeio/anytype/core_models/Object.kt +++ b/test/core-models-stub/src/main/java/com/anytypeio/anytype/core_models/Object.kt @@ -22,7 +22,8 @@ fun StubObject( isHidden: Boolean? = null, links: List = emptyList(), targetObjectType: Id? = null, - identity: Id? = null + identity: Id? = null, + fileExt: String? = null, ): ObjectWrapper.Basic = ObjectWrapper.Basic( map = mapOf( Relations.ID to id, @@ -40,7 +41,8 @@ fun StubObject( Relations.LINKS to links, Relations.TARGET_OBJECT_TYPE to targetObjectType, Relations.UNIQUE_KEY to uniqueKey, - Relations.IDENTITY to identity + Relations.IDENTITY to identity, + Relations.FILE_EXT to fileExt ) ) diff --git a/test/core-models-stub/src/main/java/com/anytypeio/anytype/presentation/MockBlockFactory.kt b/test/core-models-stub/src/main/java/com/anytypeio/anytype/presentation/MockBlockFactory.kt index 80755bf310..3b051be671 100644 --- a/test/core-models-stub/src/main/java/com/anytypeio/anytype/presentation/MockBlockFactory.kt +++ b/test/core-models-stub/src/main/java/com/anytypeio/anytype/presentation/MockBlockFactory.kt @@ -84,20 +84,6 @@ object MockBlockFactory { ) ) - fun makeFileBlock(): Block = Block( - id = MockDataFactory.randomUuid(), - fields = Block.Fields(emptyMap()), - content = Block.Content.File( - targetObjectId = MockDataFactory.randomUuid(), - name = MockDataFactory.randomString(), - state = Block.Content.File.State.DONE, - mime = MockDataFactory.randomString(), - size = MockDataFactory.randomLong(), - type = Block.Content.File.Type.FILE - ), - children = emptyList() - ) - fun makeTitleBlock(): Block = Block( id = MockDataFactory.randomUuid(), fields = Block.Fields(emptyMap()),