Library work

This commit is contained in:
Kelvin
2025-10-23 01:59:03 +02:00
parent 87d93c2ed8
commit 682b86330e
6 changed files with 321 additions and 110 deletions
@@ -8,94 +8,52 @@ import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.EditText
import android.widget.FrameLayout
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.LinearLayout.GONE
import android.widget.LinearLayout.VISIBLE
import android.widget.Spinner
import android.widget.TextView
import androidx.appcompat.widget.AppCompatImageView
import androidx.core.view.allViews
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
import com.bumptech.glide.Glide
import com.futo.platformplayer.R
import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.UISlideOverlays
import com.futo.platformplayer.api.media.PlatformID
import com.futo.platformplayer.api.media.exceptions.NoPlatformClientException
import com.futo.platformplayer.api.media.models.PlatformAuthorLink
import com.futo.platformplayer.api.media.models.ResultCapabilities
import com.futo.platformplayer.api.media.models.channels.IPlatformChannel
import com.futo.platformplayer.api.media.models.channels.SerializedChannel
import com.futo.platformplayer.api.media.models.contents.ContentType
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylist
import com.futo.platformplayer.api.media.models.post.IPlatformPost
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo
import com.futo.platformplayer.api.media.platforms.local.LocalClient
import com.futo.platformplayer.api.media.structures.AdhocPager
import com.futo.platformplayer.api.media.structures.IPager
import com.futo.platformplayer.assume
import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.constructs.Event2
import com.futo.platformplayer.constructs.Event3
import com.futo.platformplayer.constructs.TaskHandler
import com.futo.platformplayer.dp
import com.futo.platformplayer.fragment.channel.tab.ChannelAboutFragment
import com.futo.platformplayer.fragment.channel.tab.ChannelContentsFragment
import com.futo.platformplayer.fragment.channel.tab.ChannelListFragment
import com.futo.platformplayer.fragment.channel.tab.ChannelMonetizationFragment
import com.futo.platformplayer.fragment.channel.tab.ChannelPlaylistsFragment
import com.futo.platformplayer.fragment.channel.tab.IChannelTabFragment
import com.futo.platformplayer.fragment.mainactivity.main.ChannelFragment.Companion
import com.futo.platformplayer.fragment.mainactivity.main.LibraryAlbumsFragment.AlbumViewHolder
import com.futo.platformplayer.fragment.mainactivity.topbar.NavigationTopBarFragment
import com.futo.platformplayer.images.GlideHelper.Companion.crossfade
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.models.SearchType
import com.futo.platformplayer.models.Subscription
import com.futo.platformplayer.selectBestImage
import com.futo.platformplayer.selectHighestResolutionImage
import com.futo.platformplayer.states.Album
import com.futo.platformplayer.states.Artist
import com.futo.platformplayer.states.StateLibrary
import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.states.StatePlayer
import com.futo.platformplayer.states.StatePlaylists
import com.futo.platformplayer.states.StatePolycentric
import com.futo.platformplayer.states.StateSubscriptions
import com.futo.platformplayer.stores.FragmentedStorage
import com.futo.platformplayer.stores.StringStorage
import com.futo.platformplayer.toHumanNumber
import com.futo.platformplayer.views.FeedStyle
import com.futo.platformplayer.views.ToggleBar
import com.futo.platformplayer.views.adapters.AnyAdapter
import com.futo.platformplayer.views.adapters.ChannelTab
import com.futo.platformplayer.views.adapters.ChannelViewPagerAdapter
import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader
import com.futo.platformplayer.views.adapters.SubscriptionAdapter
import com.futo.platformplayer.views.adapters.viewholders.SelectablePlaylist
import com.futo.platformplayer.views.others.CreatorThumbnail
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuOverlay
import com.futo.platformplayer.views.platform.PlatformIndicator
import com.futo.platformplayer.views.subscriptions.SubscribeButton
import com.futo.polycentric.core.ApiMethods
import com.futo.polycentric.core.PolycentricProfile
import com.futo.polycentric.core.toURLInfoSystemLinkUrl
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import kotlinx.coroutines.Dispatchers
@@ -384,31 +342,14 @@ class LibraryArtistFragment : MainFragment() {
var supportsPlaylists = false;
val playlistPosition = 1
if (supportsPlaylists && !(_viewPager.adapter as ArtistViewPagerAdapter).containsItem(
ArtistTab.PLAYLISTS.ordinal.toLong()
)
) {
// keep the current tab selected
if (_viewPager.currentItem >= playlistPosition) {
_viewPager.setCurrentItem(_viewPager.currentItem + 1, false)
}
(_viewPager.adapter as ArtistViewPagerAdapter).insert(
playlistPosition,
ArtistTab.PLAYLISTS
ArtistTab.ALBUMS
)
}
if (!supportsPlaylists && (_viewPager.adapter as ArtistViewPagerAdapter).containsItem(
ArtistTab.PLAYLISTS.ordinal.toLong()
)
) {
// keep the current tab selected
if (_viewPager.currentItem >= playlistPosition) {
_viewPager.setCurrentItem(_viewPager.currentItem - 1, false)
}
(_viewPager.adapter as ArtistViewPagerAdapter).remove(playlistPosition)
}
// sets the channel for each tab
for (fragment in _fragment.childFragmentManager.fragments) {
@@ -429,15 +370,15 @@ class LibraryArtistFragment : MainFragment() {
}
}
enum class ArtistTab {
VIDEOS, PLAYLISTS
SONGS, ALBUMS
}
class ArtistViewPagerAdapter(private val fragment: LibraryArtistFragment, fragmentManager: FragmentManager, lifecycle: Lifecycle) :
FragmentStateAdapter(fragmentManager, lifecycle) {
private val _supportedFragments = mutableMapOf(
ArtistTab.VIDEOS.ordinal to ArtistTab.VIDEOS
ArtistTab.SONGS.ordinal to ArtistTab.SONGS
)
private val _tabs = arrayListOf(ArtistTab.VIDEOS)
private val _tabs = arrayListOf(ArtistTab.SONGS, ArtistTab.ALBUMS)
var artist: Artist? = null
@@ -486,35 +427,15 @@ class LibraryArtistFragment : MainFragment() {
override fun createFragment(position: Int): Fragment {
val fragment: Fragment
when (_tabs[position]) {
ArtistTab.VIDEOS -> {
ArtistTab.SONGS -> {
fragment = ChannelContentsFragment(this.fragment).apply {
/*
onContentClicked.subscribe { video, num, _ ->
this@ArtistViewPagerAdapter.onContentClicked.emit(video, num)
}
onContentUrlClicked.subscribe(this@ArtistViewPagerAdapter.onContentUrlClicked::emit)
onUrlClicked.subscribe(this@ArtistViewPagerAdapter.onUrlClicked::emit)
onChannelClicked.subscribe(this@ArtistViewPagerAdapter.onChannelClicked::emit)
onAddToClicked.subscribe(this@ArtistViewPagerAdapter.onAddToClicked::emit)
onAddToQueueClicked.subscribe(this@ArtistViewPagerAdapter.onAddToQueueClicked::emit)
onAddToWatchLaterClicked.subscribe(this@ArtistViewPagerAdapter.onAddToWatchLaterClicked::emit)
onLongPress.subscribe(this@ArtistViewPagerAdapter.onLongPress::emit)
*/
}
}
ArtistTab.PLAYLISTS -> {
fragment = ChannelPlaylistsFragment.newInstance().apply {
/*
onContentClicked.subscribe(this@ArtistViewPagerAdapter.onContentClicked::emit)
onContentUrlClicked.subscribe(this@ArtistViewPagerAdapter.onContentUrlClicked::emit)
onUrlClicked.subscribe(this@ArtistViewPagerAdapter.onUrlClicked::emit)
onChannelClicked.subscribe(this@ArtistViewPagerAdapter.onChannelClicked::emit)
onAddToClicked.subscribe(this@ArtistViewPagerAdapter.onAddToClicked::emit)
onAddToQueueClicked.subscribe(this@ArtistViewPagerAdapter.onAddToQueueClicked::emit)
onAddToWatchLaterClicked.subscribe(this@ArtistViewPagerAdapter.onAddToWatchLaterClicked::emit)
onLongPress.subscribe(this@ArtistViewPagerAdapter.onLongPress::emit)
*/
ArtistTab.ALBUMS -> {
fragment = ArtistAlbumsFragment(this.fragment).apply {
}
}
}
@@ -569,6 +490,77 @@ class LibraryArtistFragment : MainFragment() {
override fun updateSpanCount(){ }
companion object {
private const val TAG = "LibraryAlbumsFragmentsView";
}
}
class ArtistAlbumsFragment(private val frag: LibraryArtistFragment) : Fragment(), IArtistTabFragment {
var view: ArtistAlbumsView? = null;
private var _lastArtist: Artist? = null;
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
view = ArtistAlbumsView(frag, inflater);
_lastArtist?.let {
view?.setArtist(it);
}
return view;
}
override fun onDestroyView() {
view = null;
super.onDestroyView()
}
override fun setArtist(artist: Artist) {
view?.setArtist(artist);
_lastArtist = artist;
}
}
class ArtistAlbumsView : FeedView<LibraryArtistFragment, Album, Album, IPager<Album>, AlbumViewHolder> {
override val feedStyle: FeedStyle = FeedStyle.THUMBNAIL; //R.layout.list_creator;
constructor(fragment: LibraryArtistFragment, inflater: LayoutInflater) : super(fragment, inflater)
fun onShown() {
}
fun setArtist(artist: Artist) {
val initialAlbums = artist.getAlbums();
Logger.i(TAG, "Initial album count: " + initialAlbums.size);
setPager(AdhocPager({ listOf() }, initialAlbums));
}
override fun createAdapter(recyclerResults: RecyclerView, context: Context, dataset: ArrayList<Album>): InsertedViewAdapterWithLoader<AlbumViewHolder> {
return InsertedViewAdapterWithLoader(context, arrayListOf(), arrayListOf(),
childCountGetter = { dataset.size },
childViewHolderBinder = { viewHolder, position -> viewHolder.bind(dataset[position]); },
childViewHolderFactory = { viewGroup, _ ->
val holder = AlbumViewHolder(viewGroup);
holder.onClick.subscribe { c -> fragment.navigate<LibraryAlbumFragment>(c) };
return@InsertedViewAdapterWithLoader holder;
}
);
}
override fun updateSpanCount(){ }
override fun createLayoutManager(recyclerResults: RecyclerView, context: Context): GridLayoutManager {
val glmResults = GridLayoutManager(context, 1)
_swipeRefresh.layoutParams = (_swipeRefresh.layoutParams as MarginLayoutParams?)?.apply {
rightMargin = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
8.0f,
context.resources.displayMetrics
).toInt()
}
return glmResults
}
companion object {
private const val TAG = "LibraryAlbumsFragmentsView";
}
@@ -88,6 +88,19 @@ class LibraryArtistsFragment : MainFragment() {
setPager(AdhocPager<Artist>({ listOf(); }, intialArtists));
}
override fun reload() {
try {
setLoading(true);
val intialArtists = StateLibrary.instance.getArtists();
Logger.i(TAG, "Initial album count: " + intialArtists.size);
setPager(AdhocPager<Artist>({ listOf(); }, intialArtists));
}
finally {
setLoading(false);
}
}
override fun createAdapter(recyclerResults: RecyclerView, context: Context, dataset: ArrayList<Artist>): InsertedViewAdapterWithLoader<ArtistViewHolder> {
return InsertedViewAdapterWithLoader(context, arrayListOf(), arrayListOf(),
childCountGetter = { dataset.size },
@@ -123,18 +136,18 @@ class LibraryArtistsFragment : MainFragment() {
}
class ArtistViewHolder(private val _viewGroup: ViewGroup) : AnyAdapter.AnyViewHolder<Artist>(
LayoutInflater.from(_viewGroup.context).inflate(R.layout.list_album,
LayoutInflater.from(_viewGroup.context).inflate(R.layout.list_artist,
_viewGroup, false)) {
val onClick = Event1<Artist>();
protected var _artist: Artist? = null;
protected val _imageThumbnail: ImageView
//protected val _imageThumbnail: ImageView
protected val _textName: TextView
protected val _textMetadata: TextView
init {
_imageThumbnail = _view.findViewById(R.id.image_thumbnail);
//_imageThumbnail = _view.findViewById(R.id.image_thumbnail);
_textName = _view.findViewById(R.id.text_name);
_textMetadata = _view.findViewById(R.id.text_metadata);
@@ -143,6 +156,7 @@ class LibraryArtistsFragment : MainFragment() {
override fun bind(artist: Artist) {
_artist = artist;
/*
_imageThumbnail?.let {
if (artist.thumbnail != null)
Glide.with(it)
@@ -152,6 +166,7 @@ class LibraryArtistsFragment : MainFragment() {
else
Glide.with(it).load(R.drawable.placeholder_channel_thumbnail).into(it);
};
*/
_textName.text = artist.name;
_textMetadata.text = artist.countTracks?.let { "${it} tracks" } ?: "";
@@ -7,10 +7,14 @@ import android.provider.MediaStore
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.TextView
import androidx.activity.result.contract.ActivityResultContracts
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import com.futo.platformplayer.R
import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.activities.MainActivity
import com.futo.platformplayer.states.StateApp
import com.futo.platformplayer.views.buttons.BigButton
@@ -23,9 +27,12 @@ class LibraryFragment : MainFragment() {
private var view: FragView? = null;
private var allowedMusic = false;
private var allowedVideo = false;
override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): android.view.View {
val newView = FragView(this);
val newView = FragView(this, allowedMusic, allowedVideo);
view = newView;
return newView;
}
@@ -33,6 +40,9 @@ class LibraryFragment : MainFragment() {
override fun onShownWithView(parameter: Any?, isBack: Boolean) {
super.onShownWithView(parameter, isBack);
view?.onShown();
requestPermissionMusic();
requestPermissionVideo();
}
override fun onDestroyMainView() {
@@ -40,6 +50,63 @@ class LibraryFragment : MainFragment() {
super.onDestroyMainView();
}
fun setPermissionResultAudio(access: Boolean) {
allowedMusic = access;
view?.setMusicPermissions(access);
}
fun setPermissionResultVideo(access: Boolean) {
allowedVideo = access;
view?.setVideoPermissions(access);
}
fun requestPermissionMusic() {
when {
ContextCompat.checkSelfPermission(requireContext(), android.Manifest.permission.READ_MEDIA_AUDIO) == PackageManager.PERMISSION_GRANTED -> {
setPermissionResultAudio(true);
}
ActivityCompat.shouldShowRequestPermissionRationale(requireActivity(), android.Manifest.permission.READ_MEDIA_AUDIO) -> {
UIDialogs.showDialog(requireContext(), R.drawable.ic_library,
"Music permissions", "We require permissions to see your on-device music, denying this will hide the option to see local music.", null, 1,
UIDialogs.Action("Ok", {
permissionReqAudio.launch(android.Manifest.permission.READ_MEDIA_AUDIO);
}, UIDialogs.ActionStyle.PRIMARY),
UIDialogs.Action("Cancel", {
}, UIDialogs.ActionStyle.NONE));
}
else -> {
permissionReqAudio.launch(android.Manifest.permission.READ_MEDIA_AUDIO);
}
}
}
fun requestPermissionVideo() {
when {
ContextCompat.checkSelfPermission(requireContext(), android.Manifest.permission.READ_MEDIA_VIDEO) == PackageManager.PERMISSION_GRANTED -> {
setPermissionResultVideo(true);
}
ActivityCompat.shouldShowRequestPermissionRationale(requireActivity(), android.Manifest.permission.READ_MEDIA_VIDEO) -> {
UIDialogs.showDialog(requireContext(), R.drawable.ic_library, false,
"Videos permissions", "We require permissions to see your on-device videos, denying this will hide the option to see local videos.", null, 1,
UIDialogs.Action("Ok", {
permissionReqVideo.launch(android.Manifest.permission.READ_MEDIA_VIDEO);
}, UIDialogs.ActionStyle.PRIMARY),
UIDialogs.Action("Cancel", {
}, UIDialogs.ActionStyle.NONE));
}
else -> {
permissionReqVideo.launch(android.Manifest.permission.READ_MEDIA_VIDEO);
}
}
}
val permissionReqAudio = registerForActivityResult(ActivityResultContracts.RequestPermission(), { isGranted ->
setPermissionResultAudio(isGranted);
});
val permissionReqVideo = registerForActivityResult(ActivityResultContracts.RequestPermission(), { isGranted ->
setPermissionResultVideo(isGranted);
});
companion object {
fun newInstance() = LibraryFragment().apply {}
}
@@ -47,30 +114,76 @@ class LibraryFragment : MainFragment() {
class FragView: ConstraintLayout {
val fragment: LibraryFragment;
constructor(fragment: LibraryFragment, attrs : AttributeSet? = null) : super(fragment.requireContext(), attrs) {
var buttonArtists: BigButton;
var buttonAlbums: BigButton;
var buttonVideos: BigButton;
var buttonPlaylists: BigButton;
var buttonFiles: BigButton;
var metaInfo: TextView;
var allowMusic: Boolean = false;
var allowVideo: Boolean = false;
constructor(fragment: LibraryFragment, allowMusic: Boolean?, allowVideo: Boolean?) : super(fragment.requireContext()) {
inflate(context, R.layout.fragview_library, this);
this.fragment = fragment;
val buttonArtists = findViewById<BigButton>(R.id.button_artists);
val buttonAlbums = findViewById<BigButton>(R.id.button_albums);
val buttonVideos = findViewById<BigButton>(R.id.button_videos);
val buttonPlaylists = findViewById<BigButton>(R.id.button_playlists);
val buttonFiles = findViewById<BigButton>(R.id.button_files);
buttonArtists = findViewById<BigButton>(R.id.button_artists);
buttonAlbums = findViewById<BigButton>(R.id.button_albums);
buttonVideos = findViewById<BigButton>(R.id.button_videos);
buttonPlaylists = findViewById<BigButton>(R.id.button_playlists);
buttonFiles = findViewById<BigButton>(R.id.button_files);
metaInfo = findViewById(R.id.meta_info);
this.allowMusic = allowMusic ?: false;
this.allowVideo = allowVideo ?: false;
buttonArtists.onClick.subscribe {
if(this.allowMusic)
fragment.navigate<LibraryArtistsFragment>();
else
fragment.requestPermissionMusic();
}
buttonAlbums.onClick.subscribe {
if(this.allowMusic)
fragment.navigate<LibraryAlbumsFragment>();
else
fragment.requestPermissionMusic();
}
buttonVideos.onClick.subscribe {
if(this.allowVideo)
fragment.navigate<LibraryVideosFragment>();
else
fragment.requestPermissionVideo();
}
buttonPlaylists.onClick.subscribe {
fragment.navigate<PlaylistsFragment>();
}
buttonFiles.onClick.subscribe {
UIDialogs.appToast("This is gonna require a bit more work..");
}
buttonFiles.setButtonEnabled(false);
setMusicPermissions(allowMusic ?: false);
setVideoPermissions(allowVideo ?: false);
}
fun setMusicPermissions(access: Boolean) {
allowMusic = access;
buttonAlbums.setButtonEnabled(access);
buttonArtists.setButtonEnabled(access);
metaInfo.text = listOf(
if(!allowMusic) "You did not give access to local music, so these options are disabled" else null,
if(!allowVideo) "You did not give access to local videos, so these options are disabled" else null
).filterNotNull().joinToString("\n");
}
fun setVideoPermissions(access: Boolean) {
allowVideo = access;
buttonVideos.setButtonEnabled(access);
metaInfo.text = listOf(
if(!allowMusic) "You did not give access to local music, so these options are disabled" else null,
if(!allowVideo) "You did not give access to local videos, so these options are disabled" else null
).filterNotNull().joinToString("\n");
}
fun onShown() {
@@ -271,7 +271,7 @@ class Artist {
}
fun getAlbums(): List<Album> {
return listOf();
return Album.getArtistAlbums(id.toLongOrNull() ?: return listOf());
}
fun getAudioTracks(): IPager<IPlatformContent> {
@@ -438,5 +438,22 @@ class Album {
}
return list;
}
fun getArtistAlbums(artistId: Long): List<Album> {
val resolver = StateApp.instance.contextOrNull?.contentResolver;
if(resolver == null) {
Logger.w(TAG, "Album contentResolver not found");
return listOf();
}
val cursor = resolver?.query(
MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, PROJECTION, "${MediaStore.Audio.Media.ARTIST_ID} = ?", arrayOf(artistId.toString()),
MediaStore.Audio.Albums.ALBUM + " ASC") ?: return listOf();
cursor.moveToFirst();
val list = mutableListOf<Album>()
while(!cursor.isAfterLast) {
list.add(fromCursor(cursor));
cursor.moveToNext();
}
return list;
}
}
}
@@ -65,6 +65,22 @@
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginBottom="8dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="center"
android:layout_marginTop="20dp"
android:text="Library UI is temporary, and will be replaced"
android:textColor="#FFAA00" />
<TextView
android:id="@+id/meta_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="center"
android:layout_marginTop="20dp"
android:text="Library UI is temporary, and will be replaced"
android:textColor="#AAAAAA" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
+58
View File
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:id="@+id/root"
android:clickable="true">
<TextView
android:id="@+id/text_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:textSize="13dp"
android:textColor="@color/white"
android:fontFamily="@font/inter_light"
tools:text="Legendary grant recipient: Marvin Wißfeld Very Long Title That is Long"
android:maxLines="2"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toTopOf="@id/text_metadata"
android:layout_marginStart="10dp" />
<TextView
android:id="@+id/text_metadata"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:textSize="10dp"
android:textColor="@color/gray_e0"
android:fontFamily="@font/inter_extra_light"
tools:text="3 videos"
android:maxLines="1"
app:layout_constraintTop_toBottomOf="@id/text_name"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginStart="10dp" />
<!--
<ImageButton
android:id="@+id/button_play"
android:layout_width="34dp"
android:layout_height="34dp"
app:srcCompat="@drawable/ic_play_white_nopad"
android:scaleType="fitCenter"
android:padding="8dp"
app:layout_constraintRight_toLeftOf="@id/button_trash"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginEnd="10dp"/> -->
</androidx.constraintlayout.widget.ConstraintLayout>