mirror of
https://github.com/libre-tube/LibreTube.git
synced 2025-04-29 16:30:31 +05:30
Merge pull request #4979 from Bnyro/master
refactor: move description layout into its own view
This commit is contained in:
commit
a59633a726
@ -14,11 +14,9 @@ import android.os.Handler
|
|||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
import android.text.format.DateUtils
|
import android.text.format.DateUtils
|
||||||
import android.text.util.Linkify
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.TextView
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.constraintlayout.motion.widget.MotionLayout
|
import androidx.constraintlayout.motion.widget.MotionLayout
|
||||||
import androidx.constraintlayout.motion.widget.TransitionAdapter
|
import androidx.constraintlayout.motion.widget.TransitionAdapter
|
||||||
@ -27,8 +25,6 @@ import androidx.core.graphics.drawable.toDrawable
|
|||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.core.os.postDelayed
|
import androidx.core.os.postDelayed
|
||||||
import androidx.core.text.method.LinkMovementMethodCompat
|
|
||||||
import androidx.core.text.parseAsHtml
|
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
import androidx.core.view.isInvisible
|
import androidx.core.view.isInvisible
|
||||||
@ -76,7 +72,6 @@ import com.github.libretube.extensions.seekBy
|
|||||||
import com.github.libretube.extensions.serializableExtra
|
import com.github.libretube.extensions.serializableExtra
|
||||||
import com.github.libretube.extensions.setMetadata
|
import com.github.libretube.extensions.setMetadata
|
||||||
import com.github.libretube.extensions.toID
|
import com.github.libretube.extensions.toID
|
||||||
import com.github.libretube.extensions.toLocalDateSafe
|
|
||||||
import com.github.libretube.extensions.toastFromMainDispatcher
|
import com.github.libretube.extensions.toastFromMainDispatcher
|
||||||
import com.github.libretube.extensions.togglePlayPauseState
|
import com.github.libretube.extensions.togglePlayPauseState
|
||||||
import com.github.libretube.extensions.updateParameters
|
import com.github.libretube.extensions.updateParameters
|
||||||
@ -95,7 +90,6 @@ import com.github.libretube.obj.ShareData
|
|||||||
import com.github.libretube.obj.VideoResolution
|
import com.github.libretube.obj.VideoResolution
|
||||||
import com.github.libretube.parcelable.PlayerData
|
import com.github.libretube.parcelable.PlayerData
|
||||||
import com.github.libretube.ui.activities.MainActivity
|
import com.github.libretube.ui.activities.MainActivity
|
||||||
import com.github.libretube.ui.activities.VideoTagsAdapter
|
|
||||||
import com.github.libretube.ui.adapters.VideosAdapter
|
import com.github.libretube.ui.adapters.VideosAdapter
|
||||||
import com.github.libretube.ui.dialogs.AddToPlaylistDialog
|
import com.github.libretube.ui.dialogs.AddToPlaylistDialog
|
||||||
import com.github.libretube.ui.dialogs.DownloadDialog
|
import com.github.libretube.ui.dialogs.DownloadDialog
|
||||||
@ -110,8 +104,6 @@ import com.github.libretube.ui.sheets.ChaptersBottomSheet
|
|||||||
import com.github.libretube.ui.sheets.CommentsSheet
|
import com.github.libretube.ui.sheets.CommentsSheet
|
||||||
import com.github.libretube.ui.sheets.PlayingQueueSheet
|
import com.github.libretube.ui.sheets.PlayingQueueSheet
|
||||||
import com.github.libretube.ui.sheets.StatsSheet
|
import com.github.libretube.ui.sheets.StatsSheet
|
||||||
import com.github.libretube.util.HtmlParser
|
|
||||||
import com.github.libretube.util.LinkHandler
|
|
||||||
import com.github.libretube.util.NowPlayingNotification
|
import com.github.libretube.util.NowPlayingNotification
|
||||||
import com.github.libretube.util.OnlineTimeFrameReceiver
|
import com.github.libretube.util.OnlineTimeFrameReceiver
|
||||||
import com.github.libretube.util.PlayingQueue
|
import com.github.libretube.util.PlayingQueue
|
||||||
@ -391,11 +383,6 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
|
|||||||
exoPlayer.togglePlayPauseState()
|
exoPlayer.togglePlayPauseState()
|
||||||
}
|
}
|
||||||
|
|
||||||
// video description and chapters toggle
|
|
||||||
binding.playerTitleLayout.setOnClickListener {
|
|
||||||
if (this::streams.isInitialized) toggleDescription()
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.commentsToggle.setOnClickListener {
|
binding.commentsToggle.setOnClickListener {
|
||||||
// set the max height to not cover the currently playing video
|
// set the max height to not cover the currently playing video
|
||||||
commentsViewModel.handleLink = this::handleLink
|
commentsViewModel.handleLink = this::handleLink
|
||||||
@ -489,6 +476,8 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
|
|||||||
LinearLayoutManager.HORIZONTAL,
|
LinearLayoutManager.HORIZONTAL,
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
|
|
||||||
|
binding.descriptionLayout.handleLink = this::handleLink
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateMaxSheetHeight() {
|
private fun updateMaxSheetHeight() {
|
||||||
@ -577,37 +566,6 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
|
|||||||
updateResolutionOnFullscreenChange(false)
|
updateResolutionOnFullscreenChange(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun toggleDescription() {
|
|
||||||
val views = if (binding.descLinLayout.isVisible) {
|
|
||||||
// show formatted short view count
|
|
||||||
streams.views.formatShort()
|
|
||||||
} else {
|
|
||||||
// show exact view count
|
|
||||||
"%,d".format(streams.views)
|
|
||||||
}
|
|
||||||
val viewInfo = getString(R.string.normal_views, views, localizeDate(streams))
|
|
||||||
if (binding.descLinLayout.isVisible) {
|
|
||||||
// hide the description and chapters
|
|
||||||
binding.playerDescriptionArrow.animate().rotation(0F).setDuration(250).start()
|
|
||||||
binding.descLinLayout.isGone = true
|
|
||||||
|
|
||||||
// limit the title height to two lines
|
|
||||||
binding.playerTitle.maxLines = 2
|
|
||||||
} else {
|
|
||||||
// show the description and chapters
|
|
||||||
binding.playerDescriptionArrow.animate().rotation(180F).setDuration(250).start()
|
|
||||||
binding.descLinLayout.isVisible = true
|
|
||||||
|
|
||||||
// show the whole title
|
|
||||||
binding.playerTitle.maxLines = Int.MAX_VALUE
|
|
||||||
}
|
|
||||||
binding.playerViewsInfo.text = viewInfo
|
|
||||||
|
|
||||||
if (viewModel.chapters.isNotEmpty()) {
|
|
||||||
setCurrentChapterName(forceUpdate = true, enqueueNew = false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
// check whether the screen is on
|
// check whether the screen is on
|
||||||
val isInteractive = requireContext().getSystemService<PowerManager>()!!.isInteractive
|
val isInteractive = requireContext().getSystemService<PowerManager>()!!.isInteractive
|
||||||
@ -766,7 +724,14 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
|
|||||||
|
|
||||||
// set media sources for the player
|
// set media sources for the player
|
||||||
initStreamSources()
|
initStreamSources()
|
||||||
prepareExoPlayerView()
|
|
||||||
|
binding.player.apply {
|
||||||
|
useController = false
|
||||||
|
player = exoPlayer
|
||||||
|
}
|
||||||
|
|
||||||
|
playerBinding.exoProgress.setPlayer(exoPlayer)
|
||||||
|
|
||||||
initializePlayerView()
|
initializePlayerView()
|
||||||
setupSeekbarPreview()
|
setupSeekbarPreview()
|
||||||
|
|
||||||
@ -862,59 +827,21 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
|
|||||||
chaptersBottomSheet = null
|
chaptersBottomSheet = null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun prepareExoPlayerView() {
|
|
||||||
binding.player.apply {
|
|
||||||
useController = false
|
|
||||||
player = exoPlayer
|
|
||||||
}
|
|
||||||
|
|
||||||
playerBinding.exoProgress.setPlayer(exoPlayer)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun localizeDate(streams: Streams): String {
|
|
||||||
if (streams.livestream) return ""
|
|
||||||
|
|
||||||
return TextUtils.SEPARATOR + TextUtils.localizeDate(streams.uploadDate.toLocalDateSafe())
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
private fun initializePlayerView() {
|
private fun initializePlayerView() {
|
||||||
// initialize the player view actions
|
// initialize the player view actions
|
||||||
binding.player.initialize(doubleTapOverlayBinding, playerGestureControlsViewBinding)
|
binding.player.initialize(doubleTapOverlayBinding, playerGestureControlsViewBinding)
|
||||||
binding.player.initPlayerOptions(viewModel, viewLifecycleOwner, trackSelector, this)
|
binding.player.initPlayerOptions(viewModel, viewLifecycleOwner, trackSelector, this)
|
||||||
|
binding.descriptionLayout.setStreams(streams)
|
||||||
|
|
||||||
binding.apply {
|
binding.apply {
|
||||||
val views = streams.views.formatShort()
|
|
||||||
playerViewsInfo.text = getString(R.string.normal_views, views, localizeDate(streams))
|
|
||||||
|
|
||||||
textLike.text = streams.likes.formatShort()
|
|
||||||
textDislike.isVisible = streams.dislikes >= 0
|
|
||||||
textDislike.text = streams.dislikes.formatShort()
|
|
||||||
|
|
||||||
ImageHelper.loadImage(streams.uploaderAvatar, binding.playerChannelImage)
|
ImageHelper.loadImage(streams.uploaderAvatar, binding.playerChannelImage)
|
||||||
playerChannelName.text = streams.uploader
|
playerChannelName.text = streams.uploader
|
||||||
|
|
||||||
titleTextView.text = streams.title
|
titleTextView.text = streams.title
|
||||||
|
|
||||||
playerTitle.text = streams.title
|
|
||||||
playerDescription.text = streams.description
|
|
||||||
|
|
||||||
metaInfo.isVisible = streams.metaInfo.isNotEmpty()
|
|
||||||
// generate a meta info text with clickable links using html
|
|
||||||
val metaInfoText = streams.metaInfo.joinToString("\n\n") { info ->
|
|
||||||
val text = info.description.takeIf { it.isNotBlank() } ?: info.title
|
|
||||||
val links = info.urls.mapIndexed { index, url ->
|
|
||||||
"<a href=\"$url\">${info.urlTexts.getOrNull(index).orEmpty()}</a>"
|
|
||||||
}.joinToString(", ")
|
|
||||||
"$text $links"
|
|
||||||
}
|
|
||||||
metaInfo.text = metaInfoText.parseAsHtml()
|
|
||||||
|
|
||||||
playerChannelSubCount.text = context?.getString(
|
playerChannelSubCount.text = context?.getString(
|
||||||
R.string.subscribers,
|
R.string.subscribers,
|
||||||
streams.uploaderSubscriberCount.formatShort()
|
streams.uploaderSubscriberCount.formatShort()
|
||||||
)
|
)
|
||||||
|
|
||||||
player.isLive = streams.livestream
|
player.isLive = streams.livestream
|
||||||
}
|
}
|
||||||
playerBinding.exoTitle.text = streams.title
|
playerBinding.exoTitle.text = streams.title
|
||||||
@ -1034,29 +961,6 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
|
|||||||
PictureInPictureCompat.enterPictureInPictureMode(requireActivity(), pipParams)
|
PictureInPictureCompat.enterPictureInPictureMode(requireActivity(), pipParams)
|
||||||
}
|
}
|
||||||
initializeRelatedVideos(streams.relatedStreams.filter { !it.title.isNullOrBlank() })
|
initializeRelatedVideos(streams.relatedStreams.filter { !it.title.isNullOrBlank() })
|
||||||
// set video description
|
|
||||||
val description = streams.description
|
|
||||||
|
|
||||||
setupDescription(binding.playerDescription, description)
|
|
||||||
val visibility = when (streams.visibility) {
|
|
||||||
"public" -> context?.getString(R.string.visibility_public)
|
|
||||||
"unlisted" -> context?.getString(R.string.visibility_unlisted)
|
|
||||||
// currently no other visibility could be returned, might change in the future however
|
|
||||||
else -> streams.visibility.replaceFirstChar {
|
|
||||||
if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString()
|
|
||||||
}
|
|
||||||
}.orEmpty()
|
|
||||||
binding.additionalVideoInfo.text =
|
|
||||||
"${context?.getString(R.string.category)}: ${streams.category}\n" +
|
|
||||||
"${context?.getString(R.string.license)}: ${streams.license}\n" +
|
|
||||||
"${context?.getString(R.string.visibility)}: $visibility"
|
|
||||||
|
|
||||||
if (streams.tags.isNotEmpty()) {
|
|
||||||
binding.tagsRecycler.layoutManager =
|
|
||||||
LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
|
|
||||||
binding.tagsRecycler.adapter = VideoTagsAdapter(streams.tags)
|
|
||||||
}
|
|
||||||
binding.tagsRecycler.isVisible = streams.tags.isNotEmpty()
|
|
||||||
|
|
||||||
binding.playerChannel.setOnClickListener {
|
binding.playerChannel.setOnClickListener {
|
||||||
val activity = view?.context as MainActivity
|
val activity = view?.context as MainActivity
|
||||||
@ -1108,22 +1012,6 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Set up the description text with video links and timestamps
|
|
||||||
*/
|
|
||||||
private fun setupDescription(descTextView: TextView, description: String) {
|
|
||||||
// detect whether the description is html formatted
|
|
||||||
if (description.contains("<") && description.contains(">")) {
|
|
||||||
descTextView.movementMethod = LinkMovementMethodCompat.getInstance()
|
|
||||||
descTextView.text = description.replace("</a>", "</a> ")
|
|
||||||
.parseAsHtml(tagHandler = HtmlParser(LinkHandler(this::handleLink)))
|
|
||||||
} else {
|
|
||||||
// Links can be present as plain text
|
|
||||||
descTextView.autoLinkMask = Linkify.WEB_URLS
|
|
||||||
descTextView.text = description
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle a link clicked in the description
|
* Handle a link clicked in the description
|
||||||
*/
|
*/
|
||||||
|
@ -0,0 +1,144 @@
|
|||||||
|
package com.github.libretube.ui.views
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.text.util.Linkify
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import androidx.core.text.method.LinkMovementMethodCompat
|
||||||
|
import androidx.core.text.parseAsHtml
|
||||||
|
import androidx.core.view.isGone
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import com.github.libretube.R
|
||||||
|
import com.github.libretube.api.obj.Streams
|
||||||
|
import com.github.libretube.databinding.DescriptionLayoutBinding
|
||||||
|
import com.github.libretube.extensions.formatShort
|
||||||
|
import com.github.libretube.extensions.toLocalDateSafe
|
||||||
|
import com.github.libretube.ui.activities.VideoTagsAdapter
|
||||||
|
import com.github.libretube.util.HtmlParser
|
||||||
|
import com.github.libretube.util.LinkHandler
|
||||||
|
import com.github.libretube.util.TextUtils
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
class DescriptionLayout(
|
||||||
|
context: Context,
|
||||||
|
attributeSet: AttributeSet?
|
||||||
|
): LinearLayout(context, attributeSet) {
|
||||||
|
val binding = DescriptionLayoutBinding.inflate(LayoutInflater.from(context), this, true)
|
||||||
|
private var streams: Streams? = null
|
||||||
|
var handleLink: (link: String) -> Unit = {}
|
||||||
|
|
||||||
|
init {
|
||||||
|
binding.playerTitleLayout.setOnClickListener {
|
||||||
|
toggleDescription()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
fun setStreams(streams: Streams) {
|
||||||
|
this.streams = streams
|
||||||
|
|
||||||
|
val views = streams.views.formatShort()
|
||||||
|
binding.run {
|
||||||
|
playerViewsInfo.text = context.getString(R.string.normal_views, views, localizeDate(streams))
|
||||||
|
|
||||||
|
textLike.text = streams.likes.formatShort()
|
||||||
|
textDislike.isVisible = streams.dislikes >= 0
|
||||||
|
textDislike.text = streams.dislikes.formatShort()
|
||||||
|
|
||||||
|
playerTitle.text = streams.title
|
||||||
|
playerDescription.text = streams.description
|
||||||
|
|
||||||
|
metaInfo.isVisible = streams.metaInfo.isNotEmpty()
|
||||||
|
// generate a meta info text with clickable links using html
|
||||||
|
val metaInfoText = streams.metaInfo.joinToString("\n\n") { info ->
|
||||||
|
val text = info.description.takeIf { it.isNotBlank() } ?: info.title
|
||||||
|
val links = info.urls.mapIndexed { index, url ->
|
||||||
|
"<a href=\"$url\">${info.urlTexts.getOrNull(index).orEmpty()}</a>"
|
||||||
|
}.joinToString(", ")
|
||||||
|
"$text $links"
|
||||||
|
}
|
||||||
|
metaInfo.text = metaInfoText.parseAsHtml()
|
||||||
|
|
||||||
|
val visibility = when (streams.visibility) {
|
||||||
|
"public" -> context?.getString(R.string.visibility_public)
|
||||||
|
"unlisted" -> context?.getString(R.string.visibility_unlisted)
|
||||||
|
// currently no other visibility could be returned, might change in the future however
|
||||||
|
else -> streams.visibility.replaceFirstChar {
|
||||||
|
if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString()
|
||||||
|
}
|
||||||
|
}.orEmpty()
|
||||||
|
additionalVideoInfo.text =
|
||||||
|
"${context?.getString(R.string.category)}: ${streams.category}\n" +
|
||||||
|
"${context?.getString(R.string.license)}: ${streams.license}\n" +
|
||||||
|
"${context?.getString(R.string.visibility)}: $visibility"
|
||||||
|
|
||||||
|
if (streams.tags.isNotEmpty()) {
|
||||||
|
binding.tagsRecycler.layoutManager =
|
||||||
|
LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
|
||||||
|
binding.tagsRecycler.adapter = VideoTagsAdapter(streams.tags)
|
||||||
|
}
|
||||||
|
binding.tagsRecycler.isVisible = streams.tags.isNotEmpty()
|
||||||
|
|
||||||
|
setupDescription(streams.description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up the description text with video links and timestamps
|
||||||
|
*/
|
||||||
|
private fun setupDescription(description: String) {
|
||||||
|
val descTextView = binding.playerDescription
|
||||||
|
// detect whether the description is html formatted
|
||||||
|
if (description.contains("<") && description.contains(">")) {
|
||||||
|
descTextView.movementMethod = LinkMovementMethodCompat.getInstance()
|
||||||
|
descTextView.text = description.replace("</a>", "</a> ")
|
||||||
|
.parseAsHtml(tagHandler = HtmlParser(LinkHandler(handleLink)))
|
||||||
|
} else {
|
||||||
|
// Links can be present as plain text
|
||||||
|
descTextView.autoLinkMask = Linkify.WEB_URLS
|
||||||
|
descTextView.text = description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun toggleDescription() {
|
||||||
|
val streams = streams ?: return
|
||||||
|
|
||||||
|
val views = if (binding.descLinLayout.isVisible) {
|
||||||
|
// show formatted short view count
|
||||||
|
streams.views.formatShort()
|
||||||
|
} else {
|
||||||
|
// show exact view count
|
||||||
|
"%,d".format(streams.views)
|
||||||
|
}
|
||||||
|
val viewInfo = context.getString(R.string.normal_views, views, localizeDate(streams))
|
||||||
|
if (binding.descLinLayout.isVisible) {
|
||||||
|
// hide the description and chapters
|
||||||
|
binding.playerDescriptionArrow.animate().rotation(0F).setDuration(ANIMATION_DURATION).start()
|
||||||
|
binding.descLinLayout.isGone = true
|
||||||
|
|
||||||
|
// limit the title height to two lines
|
||||||
|
binding.playerTitle.maxLines = 2
|
||||||
|
} else {
|
||||||
|
// show the description and chapters
|
||||||
|
binding.playerDescriptionArrow.animate().rotation(180F).setDuration(ANIMATION_DURATION).start()
|
||||||
|
binding.descLinLayout.isVisible = true
|
||||||
|
|
||||||
|
// show the whole title
|
||||||
|
binding.playerTitle.maxLines = Int.MAX_VALUE
|
||||||
|
}
|
||||||
|
binding.playerViewsInfo.text = viewInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun localizeDate(streams: Streams): String {
|
||||||
|
if (streams.livestream) return ""
|
||||||
|
|
||||||
|
return TextUtils.SEPARATOR + TextUtils.localizeDate(streams.uploadDate.toLocalDateSafe())
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val ANIMATION_DURATION = 250L
|
||||||
|
}
|
||||||
|
}
|
124
app/src/main/res/layout/description_layout.xml
Normal file
124
app/src/main/res/layout/description_layout.xml
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout 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:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:animateLayoutChanges="true"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/player_title_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginHorizontal="4dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginVertical="8dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/player_title"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
android:layout_marginEnd="5dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:maxLines="2"
|
||||||
|
android:textSize="18sp"
|
||||||
|
tools:text="Video Title" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/player_description_arrow"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginHorizontal="10dp"
|
||||||
|
android:src="@drawable/ic_arrow_down" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="10dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/player_views_info"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="5dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
tools:text="10M views 2 days ago " />
|
||||||
|
|
||||||
|
<com.github.libretube.ui.views.DrawableTextView
|
||||||
|
android:id="@+id/textLike"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="5dp"
|
||||||
|
android:drawablePadding="5dp"
|
||||||
|
app:drawableStartCompat="@drawable/ic_like"
|
||||||
|
app:drawableStartDimen="12dp"
|
||||||
|
tools:text="4.2K" />
|
||||||
|
|
||||||
|
<com.github.libretube.ui.views.DrawableTextView
|
||||||
|
android:id="@+id/textDislike"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="5dp"
|
||||||
|
android:drawablePadding="5dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:drawableStartCompat="@drawable/ic_dislike"
|
||||||
|
app:drawableStartDimen="12dp"
|
||||||
|
tools:text="1.3K" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/desc_linLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="5dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/meta_info"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/player_description"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:textIsSelectable="true"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/additional_video_info"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:textIsSelectable="true"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/tags_recycler"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
@ -22,122 +22,13 @@
|
|||||||
android:id="@+id/linLayout"
|
android:id="@+id/linLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:animateLayoutChanges="true"
|
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<LinearLayout
|
<com.github.libretube.ui.views.DescriptionLayout
|
||||||
android:id="@+id/player_title_layout"
|
android:id="@+id/descriptionLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_vertical"
|
android:animateLayoutChanges="true"/>
|
||||||
android:layout_marginHorizontal="4dp"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginVertical="8dp"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/player_title"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_marginStart="10dp"
|
|
||||||
android:layout_marginEnd="5dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:maxLines="2"
|
|
||||||
android:textSize="18sp"
|
|
||||||
tools:text="Video Title" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/player_description_arrow"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_marginHorizontal="10dp"
|
|
||||||
android:src="@drawable/ic_arrow_down" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginHorizontal="10dp"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/player_views_info"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="5dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
tools:text="10M views 2 days ago " />
|
|
||||||
|
|
||||||
<com.github.libretube.ui.views.DrawableTextView
|
|
||||||
android:id="@+id/textLike"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginHorizontal="5dp"
|
|
||||||
android:drawablePadding="5dp"
|
|
||||||
app:drawableStartCompat="@drawable/ic_like"
|
|
||||||
app:drawableStartDimen="12dp"
|
|
||||||
tools:text="4.2K" />
|
|
||||||
|
|
||||||
<com.github.libretube.ui.views.DrawableTextView
|
|
||||||
android:id="@+id/textDislike"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginHorizontal="5dp"
|
|
||||||
android:drawablePadding="5dp"
|
|
||||||
app:drawableStartCompat="@drawable/ic_dislike"
|
|
||||||
app:drawableStartDimen="12dp"
|
|
||||||
android:visibility="gone"
|
|
||||||
tools:text="1.3K" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/desc_linLayout"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginHorizontal="5dp"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:visibility="gone">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/meta_info"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:padding="8dp"
|
|
||||||
android:textSize="14sp"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/player_description"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:padding="8dp"
|
|
||||||
android:textIsSelectable="true"
|
|
||||||
android:textSize="14sp" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/additional_video_info"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:padding="8dp"
|
|
||||||
android:textIsSelectable="true"
|
|
||||||
android:textSize="14sp" />
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/tags_recycler"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
<com.google.android.material.card.MaterialCardView
|
||||||
style="@style/Widget.Material3.CardView.Elevated"
|
style="@style/Widget.Material3.CardView.Elevated"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user