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

Droid 3265 Editor | Feature | Support video file layout (#2189)

Co-authored-by: Evgenii Kozlov <enklave.mare.balticum@protonmail.com>
This commit is contained in:
Nadezhda-Gurova 2025-04-04 19:10:38 +02:00 committed by GitHub
parent a56b7c0703
commit ec76873060
Signed by: github
GPG key ID: B5690EEEBB952194
10 changed files with 299 additions and 4 deletions

View file

@ -103,6 +103,7 @@ object Relations {
const val WRITERS_LIMIT = "writersLimit"
const val SHARED_SPACES_LIMIT = "sharedSpacesLimit"
const val FILE_ID = "fileId"
const val LAYOUT_ALIGN = "layoutAlign"
@ -203,6 +204,7 @@ object Relations {
"layoutWidth",
"defaultViewType",
"defaultTypeId",
"resolvedLayout"
"resolvedLayout",
"fileId",
)
}

View file

@ -59,6 +59,7 @@ 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.ItemBlockTitleVideoBinding
import com.anytypeio.anytype.core_ui.databinding.ItemBlockTocBinding
import com.anytypeio.anytype.core_ui.databinding.ItemBlockToggleBinding
import com.anytypeio.anytype.core_ui.databinding.ItemBlockUnsupportedBinding
@ -194,6 +195,7 @@ import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_VIDEO
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_VIDEO_ERROR
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_VIDEO_PLACEHOLDER
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_VIDEO_TITLE
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_VIDEO_UPLOAD
import com.anytypeio.anytype.presentation.editor.editor.slash.SlashEvent
import com.anytypeio.anytype.presentation.objects.ObjectIcon
@ -271,6 +273,9 @@ class BlockAdapter(
is Video -> {
holder.recycle()
}
is Title.Video -> {
holder.release()
}
}
}
@ -370,6 +375,11 @@ class BlockAdapter(
ItemBlockTitleFileBinding.inflate(inflater, parent, false)
)
}
HOLDER_VIDEO_TITLE -> Title.Video(
ItemBlockTitleVideoBinding.inflate(inflater, parent, false),
lifecycle
)
HOLDER_TODO_TITLE -> {
Title.Todo(
ItemBlockTitleTodoBinding.inflate(inflater, parent, false)
@ -1026,6 +1036,13 @@ class BlockAdapter(
item = blocks[position] as BlockView.Title.File
)
}
is Title.Video -> {
holder.processPayloads(
payloads = payloads.typeOf(),
item = blocks[position] as BlockView.Title.Video
)
}
is Numbered -> {
holder.processChangePayload(
payloads = payloads.typeOf(),
@ -1390,7 +1407,13 @@ class BlockAdapter(
}
is Title.File -> {
holder.apply {
bind(item = blocks[position] as BlockView.Title.File,)
bind(item = blocks[position] as BlockView.Title.File)
}
}
is Title.Video -> {
holder.apply {
bind(item = blocks[position] as BlockView.Title.Video)
}
}
is Code -> {

View file

@ -12,6 +12,9 @@ import android.widget.TextView
import androidx.compose.ui.platform.ComposeView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.RecyclerView
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.common.SearchHighlightSpan
@ -20,6 +23,7 @@ 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.ItemBlockTitleVideoBinding
import com.anytypeio.anytype.core_ui.extensions.setBlockBackgroundColor
import com.anytypeio.anytype.core_ui.features.editor.BlockViewDiffUtil
import com.anytypeio.anytype.core_ui.features.editor.BlockViewHolder
@ -41,6 +45,10 @@ import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.target.Target
import com.google.android.exoplayer2.DefaultLoadControl
import com.google.android.exoplayer2.ExoPlayer
import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.ui.StyledPlayerView
import com.bumptech.glide.request.transition.Transition
import timber.log.Timber
@ -645,4 +653,95 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder {
//do nothing
}
}
class Video(
private val videoBinding: ItemBlockTitleVideoBinding,
lifecycle: Lifecycle
) : Title(videoBinding.root), LifecycleEventObserver {
override val icon: ObjectIconWidget = videoBinding.objectIconWidget
override val image: ImageView = videoBinding.cover
override val content: TextInputWidget = videoBinding.title
override val selectionView: View = itemView
private var player: ExoPlayer? = null
private var videoUrl: String? = null
private val playerView: StyledPlayerView = videoBinding.playerView
private val playButton: ImageView = videoBinding.playButton
init {
lifecycle.addObserver(this)
}
fun bind(item: BlockView.Title.Video) {
super.bind(
item = item,
onCoverClicked = {},
click = {}
)
content.setText(item.text)
setupPreview(item)
}
private fun setupPreview(item: BlockView.Title.Video) {
videoUrl = item.videoUrl
with(videoBinding) {
objectIconWidget.gone()
playerView.visible()
playButton.visible()
playButton.setOnClickListener { togglePlayback() }
}
}
private fun togglePlayback() {
videoUrl?.let { url ->
if (player?.isPlaying == true) pause() else play(url)
}
}
fun play(url: String) {
release()
player = ExoPlayer.Builder(itemView.context)
.setLoadControl(
DefaultLoadControl.Builder()
.setBufferDurationsMs(600, 1000, 250, 500)
.build()
)
.build()
.apply {
playerView.player = this
setMediaItem(MediaItem.fromUri(url))
prepare()
playWhenReady = true
}
playButton.gone()
}
fun pause() {
player?.pause()
videoBinding.playButton.visible()
}
fun release() {
player?.release()
player = null
}
override fun applyTextColor(item: BlockView.Title) {
}
override fun applyBackground(item: BlockView.Title) {
}
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
when (event) {
Lifecycle.Event.ON_PAUSE -> pause()
Lifecycle.Event.ON_DESTROY -> release()
else -> {}
}
}
}
}

View file

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="64"
android:viewportHeight="64">
<path
android:pathData="M32,0L32,0A32,32 0,0 1,64 32L64,32A32,32 0,0 1,32 64L32,64A32,32 0,0 1,0 32L0,32A32,32 0,0 1,32 0z"
android:strokeAlpha="0.7"
android:fillColor="@color/background_primary"
android:fillAlpha="0.7"/>
<path
android:pathData="M24,46V18L48,32L24,46Z"
android:fillColor="@color/text_primary"/>
</vector>

View file

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/playerContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="16dp"
app:cardElevation="0dp"
android:layout_marginEnd="20dp"
app:cardPreventCornerOverlap="false">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/cover"
android:layout_width="0dp"
android:layout_height="200dp"
android:scaleType="centerCrop"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/videoContainer"
android:layout_width="0dp"
android:layout_height="200dp"
android:layout_marginTop="@dimen/dp_48"
app:cardCornerRadius="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cover"
app:strokeColor="@color/shape_tertiary"
app:strokeWidth="1dp">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="1dp">
<com.google.android.exoplayer2.ui.StyledPlayerView
android:id="@+id/playerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent"
app:resize_mode="fit"
app:use_controller="true" />
<ImageView
android:id="@+id/playButton"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center"
android:src="@drawable/play"
android:visibility="gone" />
</FrameLayout>
</com.google.android.material.card.MaterialCardView>
<com.anytypeio.anytype.core_ui.widgets.ObjectIconWidget
android:id="@+id/objectIconWidget"
android:layout_width="64dp"
android:layout_height="80dp"
android:layout_marginStart="28dp"
android:layout_marginTop="16dp"
android:background="@drawable/bg_title_file_icon"
android:clickable="true"
android:focusable="true"
android:transitionName="@string/logo_transition"
app:imageSize="80dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/videoContainer"
tools:src="@drawable/ic_mime_pdf" />
<com.anytypeio.anytype.core_ui.widgets.text.TextInputWidget
android:id="@+id/title"
style="@style/BlockTitleContentStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:hint="@string/untitled"
android:paddingTop="0dp"
app:ignoreDragAndDrop="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/objectIconWidget"
app:onlyPasteAsPlaneText="true"
tools:text="Check new Android version multiline"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>

View file

@ -72,6 +72,7 @@ import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_VIDEO
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_VIDEO_ERROR
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_VIDEO_PLACEHOLDER
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_VIDEO_TITLE
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_VIDEO_UPLOAD
import com.anytypeio.anytype.presentation.objects.ObjectIcon
import com.anytypeio.anytype.presentation.objects.appearance.choose.ObjectAppearanceChooseSettingsView
@ -688,11 +689,39 @@ sealed class BlockView : ViewType {
override var cursor: Int? = null,
override val searchFields: List<Searchable.Field> = emptyList(),
override val hint: String? = null,
val url: String?,
val icon: ObjectIcon
) : Title(), Searchable {
override fun getViewType() = HOLDER_FILE_TITLE
}
/**
* UI-model for a video-layout title block.
* @property id block's id
* @property text text content (i.e. title text)
* @property videoUrl direct URL to video content
*/
data class Video(
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<Searchable.Field> = emptyList(),
override val hint: String? = null,
val videoUrl: String?,
val icon: ObjectIcon
) : Title(), Searchable {
override fun getViewType() = HOLDER_VIDEO_TITLE
}
/**
* UI-model for a profile-layout title block.
* @property id block's id
@ -1433,6 +1462,7 @@ sealed class BlockView : ViewType {
@JvmInline
value class RowId(val value: String)
@JvmInline
value class RowIndex(val value: Int)
@ -1440,6 +1470,7 @@ sealed class BlockView : ViewType {
@JvmInline
value class ColumnId(val value: String)
@JvmInline
value class ColumnIndex(val value: Int)

View file

@ -6,6 +6,7 @@ object Types {
const val HOLDER_PROFILE_TITLE = 35
const val HOLDER_ARCHIVE_TITLE = 36
const val HOLDER_FILE_TITLE = 37
const val HOLDER_VIDEO_TITLE = 60
const val HOLDER_TODO_TITLE = 48
const val HOLDER_HEADER_ONE = 2
const val HOLDER_HEADER_TWO = 3

View file

@ -29,6 +29,9 @@ import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView.Appearance.InEditor
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView.Mode
import com.anytypeio.anytype.presentation.editor.toggle.ToggleStateHolder
import com.anytypeio.anytype.presentation.mapper.getFileUrl
import com.anytypeio.anytype.presentation.mapper.marks
import com.anytypeio.anytype.presentation.extension.getTypeForObject
import com.anytypeio.anytype.presentation.mapper.objectIcon
import com.anytypeio.anytype.presentation.mapper.marks
import com.anytypeio.anytype.presentation.mapper.toFileView
@ -1497,8 +1500,24 @@ class DefaultBlockViewRenderer @Inject constructor(
color = block.textColor()
)
}
ObjectType.Layout.VIDEO -> {
BlockView.Title.Video(
mode = Mode.READ,
id = block.id,
text = fieldParser.getObjectName(currentObject),
videoUrl = currentObject.getFileUrl(urlBuilder),
icon = currentObject.objectIcon(builder = urlBuilder),
isFocused = resolveIsFocused(focus, block),
cursor = cursor,
coverColor = coverContainer.coverColor,
coverImage = coverContainer.coverImage,
coverGradient = coverContainer.coverGradient,
background = block.parseThemeBackgroundColor(),
color = block.textColor()
)
}
ObjectType.Layout.FILE,
ObjectType.Layout.VIDEO,
ObjectType.Layout.AUDIO,
ObjectType.Layout.PDF -> {
val objType = storeOfObjectTypes.getTypeOfObject(currentObject)
@ -1513,7 +1532,8 @@ class DefaultBlockViewRenderer @Inject constructor(
coverGradient = coverContainer.coverGradient,
background = block.parseThemeBackgroundColor(),
color = block.textColor(),
icon = currentObject.objectIcon(builder = urlBuilder, objType = objType)
icon = currentObject.objectIcon(builder = urlBuilder, objType = objType),
url = currentObject.getFileUrl(urlBuilder)
)
}
ObjectType.Layout.IMAGE -> {

View file

@ -355,6 +355,7 @@ fun List<BlockView>.toggleTableMode(
is BlockView.DataView.Deleted -> view.copy(isSelected = false)
is BlockView.ButtonOpenFile.ImageButton -> view
is BlockView.ButtonOpenFile.FileButton -> view
is BlockView.Title.Video -> view
}
}
}

View file

@ -2,6 +2,7 @@ package com.anytypeio.anytype.presentation.mapper
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.presentation.objects.ObjectIcon
import com.anytypeio.anytype.presentation.objects.ObjectIcon.Basic
@ -144,3 +145,8 @@ private fun ObjectWrapper.Type.objectFallbackIcon(): ObjectIcon.TypeIcon.Fallbac
fun ObjectWrapper.Basic.objectIcon(builder: UrlBuilder): ObjectIcon {
return ObjectIcon.None
}
fun ObjectWrapper.Basic.getFileUrl(urlBuilder: UrlBuilder): String? {
val fileId = getValue<String>(Relations.FILE_ID) ?: return null
return urlBuilder.file(fileId)
}