Merge branch 'master' of gitlab.futo.org:videostreaming/grayjay

This commit is contained in:
Koen J
2025-11-25 12:33:16 +01:00
22 changed files with 643 additions and 82 deletions
@@ -8,18 +8,25 @@ import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.res.Configuration import android.content.res.Configuration
import android.os.Build
import android.os.Bundle import android.os.Bundle
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.* import android.widget.*
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.animation.doOnEnd import androidx.core.animation.doOnEnd
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.futo.platformplayer.R import com.futo.platformplayer.R
import com.futo.platformplayer.Settings import com.futo.platformplayer.Settings
import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.activities.MainActivity import com.futo.platformplayer.activities.MainActivity
import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.dp import com.futo.platformplayer.dp
import com.futo.platformplayer.fragment.mainactivity.MainActivityFragment import com.futo.platformplayer.fragment.mainactivity.MainActivityFragment
import com.futo.platformplayer.fragment.mainactivity.main.* import com.futo.platformplayer.fragment.mainactivity.main.*
@@ -27,6 +34,10 @@ import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.states.StateApp
import com.futo.platformplayer.states.StatePayment import com.futo.platformplayer.states.StatePayment
import com.futo.platformplayer.states.StateSubscriptions import com.futo.platformplayer.states.StateSubscriptions
import com.futo.platformplayer.views.AnyAdapterView
import com.futo.platformplayer.views.AnyAdapterView.Companion.asAny
import com.futo.platformplayer.views.adapters.AnyAdapter
import com.futo.platformplayer.views.pills.RoundButton
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.math.floor import kotlin.math.floor
@@ -69,9 +80,15 @@ class MenuBottomBarFragment : MainActivityFragment() {
private val _inflater: LayoutInflater; private val _inflater: LayoutInflater;
private val _subscribedActivity: MainActivity?; private val _subscribedActivity: MainActivity?;
private val _containerMoreHeader: ConstraintLayout;
private val _toggleAirplaneMode: LinearLayout;
private val _togglePrivacy: LinearLayout;
private var _overlayMore: FrameLayout; private var _overlayMore: FrameLayout;
private var _overlayMoreBackground: FrameLayout; private var _overlayMoreBackground: FrameLayout;
private var _layoutMoreButtons: LinearLayout; private var _layoutMoreButtons: RecyclerView;
private val _layoutMoreButtonItems = arrayListOf<MenuButtonItem>();
private var _layoutMoreButtonsAdapter: AnyAdapterView<MenuButtonItem, MenuButtonItemViewHolder>;
private var _layoutBottomBarButtons: LinearLayout; private var _layoutBottomBarButtons: LinearLayout;
private var _moreVisible = false; private var _moreVisible = false;
@@ -90,10 +107,71 @@ class MenuBottomBarFragment : MainActivityFragment() {
_inflater = inflater; _inflater = inflater;
inflater.inflate(R.layout.fragment_overview_bottom_bar, this); inflater.inflate(R.layout.fragment_overview_bottom_bar, this);
_containerMoreHeader = findViewById(R.id.container_more_options);
_toggleAirplaneMode = findViewById(R.id.container_toggle_airplane);
_togglePrivacy = findViewById(R.id.container_toggle_privacy);
_toggleAirplaneMode.isVisible = false //TODO: Remove when airplane mode implemented
StateApp.instance.airplaneModeChanged.subscribe {
if(!StateApp.instance.airplaneMode)
_toggleAirplaneMode.setBackgroundResource(R.drawable.background_menu_toggle)
else
_toggleAirplaneMode.setBackgroundResource(R.drawable.background_menu_toggle_active)
}
if(!StateApp.instance.airplaneMode)
_toggleAirplaneMode.setBackgroundResource(R.drawable.background_menu_toggle)
else
_toggleAirplaneMode.setBackgroundResource(R.drawable.background_menu_toggle_active)
_toggleAirplaneMode.setOnClickListener {
if(StateApp.instance.airplaneMode) {
StateApp.instance.setAirMode(false);
UIDialogs.appToast("Airplane mode disabled");
}
else {
StateApp.instance.setAirMode(true);
UIDialogs.appToast("Airplane mode enabled");
}
}
StateApp.instance.privateModeChanged.subscribe {
if(!StateApp.instance.privateMode)
_togglePrivacy.setBackgroundResource(R.drawable.background_menu_toggle)
else
_togglePrivacy.setBackgroundResource(R.drawable.background_menu_toggle_active)
}
if(!StateApp.instance.privateMode)
_togglePrivacy.setBackgroundResource(R.drawable.background_menu_toggle)
else
_togglePrivacy.setBackgroundResource(R.drawable.background_menu_toggle_active)
_togglePrivacy.setOnClickListener {
if(StateApp.instance.privateMode) {
StateApp.instance.setPrivacyMode(false);
UIDialogs.appToast("Privacy mode disabled");
}
else {
StateApp.instance.setPrivacyMode(true);
UIDialogs.appToast("Privacy mode enabled");
}
}
_overlayMore = findViewById(R.id.more_overlay); _overlayMore = findViewById(R.id.more_overlay);
_overlayMoreBackground = findViewById(R.id.more_overlay_background); _overlayMoreBackground = findViewById(R.id.more_overlay_background);
_layoutMoreButtons = findViewById(R.id.more_menu_buttons); _layoutMoreButtons = findViewById(R.id.more_menu_buttons);
_layoutBottomBarButtons = findViewById(R.id.bottom_bar_buttons) _layoutBottomBarButtons = findViewById(R.id.bottom_bar_buttons);
val totalWidthDp = resources.displayMetrics.widthPixels / resources.displayMetrics.density;
val columns = MenuButtonItemViewHolder.getAutoSizeColumns(totalWidthDp);
_layoutMoreButtonsAdapter = _layoutMoreButtons.asAny<MenuButtonItem, MenuButtonItemViewHolder>(_layoutMoreButtonItems,
RecyclerView.VERTICAL, false, { button ->
button.setAutoSize(totalWidthDp);
button.parentFragment = this@MenuBottomBarView._fragment;
button.onClick.subscribe {
setMoreVisible(false);
}
})
val layoutManager = GridLayoutManager(context, columns, GridLayoutManager.VERTICAL, true);
_layoutMoreButtons.layoutManager = layoutManager;
_overlayMoreBackground.setOnClickListener { setMoreVisible(false); }; _overlayMoreBackground.setOnClickListener { setMoreVisible(false); };
@@ -120,6 +198,8 @@ class MenuBottomBarFragment : MainActivityFragment() {
} }
private fun setMoreVisible(visible: Boolean) { private fun setMoreVisible(visible: Boolean) {
//TODO: issues with these bools
if (_moreVisibleAnimating) { if (_moreVisibleAnimating) {
return return
} }
@@ -128,9 +208,12 @@ class MenuBottomBarFragment : MainActivityFragment() {
return return
} }
/*
val height = _moreButtons.firstOrNull()?.let { val height = _moreButtons.firstOrNull()?.let {
it.height.toFloat() + (it.layoutParams as MarginLayoutParams).bottomMargin it.height.toFloat() + (it.layoutParams as MarginLayoutParams).bottomMargin
} ?: return } ?: return
*/
_moreVisibleAnimating = true _moreVisibleAnimating = true
val moreOverlayBackground = _overlayMoreBackground val moreOverlayBackground = _overlayMoreBackground
@@ -142,14 +225,17 @@ class MenuBottomBarFragment : MainActivityFragment() {
moreOverlay.visibility = VISIBLE moreOverlay.visibility = VISIBLE
val animations = arrayListOf<Animator>() val animations = arrayListOf<Animator>()
animations.add(ObjectAnimator.ofFloat(moreOverlayBackground, "alpha", 0.0f, 1.0f).setDuration(duration)) animations.add(ObjectAnimator.ofFloat(moreOverlayBackground, "alpha", 0.0f, 1.0f).setDuration(duration))
animations.add(ObjectAnimator.ofFloat(_layoutMoreButtons, "alpha", 0.0f, 1.0f).setDuration(duration))
animations.add(ObjectAnimator.ofFloat(_containerMoreHeader, "alpha", 0.0f, 1.0f).setDuration(duration))
_bottomButtons.find { it.definition.id == 99 }?.let { _bottomButtons.find { it.definition.id == 99 }?.let {
animations.add(ObjectAnimator.ofFloat(it, "alpha", 0.5f, 1.0f) animations.add(ObjectAnimator.ofFloat(it, "alpha", 0.5f, 1.0f)
.setDuration(duration)); .setDuration(duration));
} }
animations.add(ObjectAnimator.ofFloat(_layoutMoreButtons, "translationY", resources.displayMetrics.heightPixels.toFloat(), 0.0f).setDuration(duration))
for ((index, button) in _moreButtons.withIndex()) { for ((index, button) in _moreButtons.withIndex()) {
val i = _moreButtons.size - index val i = _moreButtons.size - index
animations.add(ObjectAnimator.ofFloat(button, "translationY", height * staggerFactor * (i + 1), 0.0f).setDuration(duration)) //animations.add(ObjectAnimator.ofFloat(button, "translationY", height * staggerFactor * (i + 1), 0.0f).setDuration(duration))
} }
val animatorSet = AnimatorSet() val animatorSet = AnimatorSet()
@@ -164,14 +250,21 @@ class MenuBottomBarFragment : MainActivityFragment() {
animations animations
.add(ObjectAnimator.ofFloat(moreOverlayBackground, "alpha", 1.0f, 0.0f) .add(ObjectAnimator.ofFloat(moreOverlayBackground, "alpha", 1.0f, 0.0f)
.setDuration(duration)) .setDuration(duration))
animations
.add(ObjectAnimator.ofFloat(_layoutMoreButtons, "alpha", 1.0f, 0.0f)
.setDuration(duration))
animations
.add(ObjectAnimator.ofFloat(_containerMoreHeader, "alpha", 1.0f, 0.0f)
.setDuration(duration))
_bottomButtons.find { it.definition.id == 99 }?.let { _bottomButtons.find { it.definition.id == 99 }?.let {
animations.add(ObjectAnimator.ofFloat(it, "alpha", 1.0f, 0.5f) animations.add(ObjectAnimator.ofFloat(it, "alpha", 1.0f, 0.5f)
.setDuration(duration)); .setDuration(duration));
} }
animations.add(ObjectAnimator.ofFloat(_layoutMoreButtons, "translationY", 0.0f, resources.displayMetrics.heightPixels.toFloat()).setDuration(duration))
for ((index, button) in _moreButtons.withIndex()) { for ((index, button) in _moreButtons.withIndex()) {
val i = _moreButtons.size - index val i = _moreButtons.size - index
animations.add(ObjectAnimator.ofFloat(button, "translationY", 0.0f, height * staggerFactor * (i + 1)).setDuration(duration)) //animations.add(ObjectAnimator.ofFloat(button, "translationY", 0.0f, height * staggerFactor * (i + 1)).setDuration(duration))
} }
val animatorSet = AnimatorSet() val animatorSet = AnimatorSet()
@@ -183,11 +276,12 @@ class MenuBottomBarFragment : MainActivityFragment() {
animatorSet.playTogether(animations) animatorSet.playTogether(animations)
animatorSet.start() animatorSet.start()
} }
} }
private fun updateBottomMenuButtons(buttons: MutableList<ButtonDefinition>, hasMore: Boolean) { private fun updateBottomMenuButtons(buttons: MutableList<ButtonDefinition>, hasMore: Boolean) {
if (hasMore) { if (hasMore) {
buttons.add(ButtonDefinition(99, R.drawable.ic_more, R.drawable.ic_more, R.string.more, canToggle = false, { false }, { setMoreVisible(true) })) buttons.add(ButtonDefinition(99, R.drawable.ic_more, R.drawable.ic_more, R.string.more, canToggle = false, { false }, { setMoreVisible(!_moreVisible) }))
} }
_bottomButtons.clear(); _bottomButtons.clear();
@@ -252,7 +346,9 @@ class MenuBottomBarFragment : MainActivityFragment() {
insertedButtons++; insertedButtons++;
} }
val newButtons = mutableListOf<MenuButtonItem>();
for (data in buttons) { for (data in buttons) {
/*
val button = MenuButton(context, data, _fragment, true); val button = MenuButton(context, data, _fragment, true);
button.setOnClickListener { button.setOnClickListener {
updateMenuIcons() updateMenuIcons()
@@ -262,7 +358,12 @@ class MenuBottomBarFragment : MainActivityFragment() {
_moreButtons.add(button); _moreButtons.add(button);
_layoutMoreButtons.addView(button); _layoutMoreButtons.addView(button);
*/
val buttonItem = MenuButtonItem(data);
newButtons.add(buttonItem);
} }
_layoutMoreButtonsAdapter.setData(newButtons);
_layoutMoreButtonsAdapter.notifyContentChanged();
} }
private fun updateMenuIcons() { private fun updateMenuIcons() {
@@ -350,6 +451,71 @@ class MenuBottomBarFragment : MainActivityFragment() {
} }
class MenuButtonItem(val def: ButtonDefinition);
class MenuButtonItemViewHolder(private val _viewGroup: ViewGroup): AnyAdapter.AnyViewHolder<MenuButtonItem>(
LayoutInflater.from(_viewGroup.context).inflate(R.layout.list_menu_tile,
_viewGroup, false)) {
val onClick = Event1<MenuButtonItem>();
val root: ConstraintLayout;
val imageIcon: ImageView;
val textName: TextView;
var button: MenuButtonItem? = null;
var parentFragment: MenuBottomBarFragment? = null;
init {
root = _view.findViewById(R.id.root);
imageIcon = _view.findViewById(R.id.image_icon);
textName = _view.findViewById(R.id.text_name);
root.setOnClickListener {
button?.let {
it.def.action(parentFragment ?: return@let);
onClick.emit(it);
}
}
}
override fun bind(value: MenuButtonItem) {
button = value;
textName.text = _view.context.getString(value.def.string);
imageIcon.setImageResource(value.def.iconActive);
}
fun setWidth(dp: Int) {
root.updateLayoutParams {
this.width = (dp - 6).dp(_viewGroup.context.resources);
this.height = (dp - 6).dp(_viewGroup.context.resources);
}
imageIcon.updateLayoutParams {
this.width = (dp - 54).dp(_viewGroup.context.resources);
this.height = (dp - 54).dp(_viewGroup.context.resources);
}
}
fun setAutoSize(totalWidth: Float) {
val dpWidth = totalWidth;
val columns = Math.max(((dpWidth) / viewWidthDp).toInt(), 1);
val remainder = dpWidth - columns * viewWidthDp;
val targetSize = viewWidthDp + (remainder / columns).toInt();
setWidth(targetSize);
}
companion object {
val viewWidthDp = 90;
fun getAutoSizeColumns(totalWidth: Float): Int {
val dpWidth = totalWidth;
val columns = Math.max(((dpWidth) / viewWidthDp).toInt(), 1);
return columns;
}
}
}
class MenuButton: LinearLayout { class MenuButton: LinearLayout {
val definition: ButtonDefinition; val definition: ButtonDefinition;
@@ -413,7 +579,9 @@ class MenuBottomBarFragment : MainActivityFragment() {
} }
}), }),
ButtonDefinition(1, R.drawable.ic_subscriptions, R.drawable.ic_subscriptions_filled, R.string.subscriptions, canToggle = true, { it.currentMain is SubscriptionsFeedFragment }, { it.navigate<SubscriptionsFeedFragment>(withHistory = false) }), ButtonDefinition(1, R.drawable.ic_subscriptions, R.drawable.ic_subscriptions_filled, R.string.subscriptions, canToggle = true, { it.currentMain is SubscriptionsFeedFragment }, { it.navigate<SubscriptionsFeedFragment>(withHistory = false) }),
ButtonDefinition(12, R.drawable.ic_library, R.drawable.ic_library, R.string.library, canToggle = false, { it.currentMain is LibraryFragment }, { it.navigate<LibraryFragment>(withHistory = false) }), //if(Build.VERSION.SDK_INT > Build.VERSION_CODES.P)
ButtonDefinition(12, R.drawable.ic_library, R.drawable.ic_library, R.string.library, canToggle = false, { it.currentMain is LibraryFragment }, { it.navigate<LibraryFragment>(withHistory = false) })
,//else null,
ButtonDefinition(2, R.drawable.ic_creators, R.drawable.ic_creators_filled, R.string.creators, canToggle = false, { it.currentMain is CreatorsFragment }, { it.navigate<CreatorsFragment>(withHistory = false) }), ButtonDefinition(2, R.drawable.ic_creators, R.drawable.ic_creators_filled, R.string.creators, canToggle = false, { it.currentMain is CreatorsFragment }, { it.navigate<CreatorsFragment>(withHistory = false) }),
ButtonDefinition(3, R.drawable.ic_sources, R.drawable.ic_sources_filled, R.string.sources, canToggle = false, { it.currentMain is SourcesFragment }, { it.navigate<SourcesFragment>(withHistory = false) }), ButtonDefinition(3, R.drawable.ic_sources, R.drawable.ic_sources_filled, R.string.sources, canToggle = false, { it.currentMain is SourcesFragment }, { it.navigate<SourcesFragment>(withHistory = false) }),
ButtonDefinition(4, R.drawable.ic_playlist, R.drawable.ic_playlist_filled, R.string.playlists, canToggle = false, { it.currentMain is PlaylistsFragment }, { it.navigate<PlaylistsFragment>(withHistory = false) }), ButtonDefinition(4, R.drawable.ic_playlist, R.drawable.ic_playlist_filled, R.string.playlists, canToggle = false, { it.currentMain is PlaylistsFragment }, { it.navigate<PlaylistsFragment>(withHistory = false) }),
@@ -451,7 +619,7 @@ class MenuBottomBarFragment : MainActivityFragment() {
//96 is reserved for privacy button //96 is reserved for privacy button
//98 is reserved for buy button //98 is reserved for buy button
//99 is reserved for more button //99 is reserved for more button
); ).filterNotNull();
} }
data class ButtonDefinition( data class ButtonDefinition(
@@ -14,6 +14,7 @@ import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.widget.AppCompatImageView import androidx.appcompat.widget.AppCompatImageView
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
@@ -22,6 +23,7 @@ import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2 import androidx.viewpager2.widget.ViewPager2
import com.bumptech.glide.Glide
import com.futo.platformplayer.R import com.futo.platformplayer.R
import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.UISlideOverlays import com.futo.platformplayer.UISlideOverlays
@@ -359,7 +361,21 @@ class LibraryArtistFragment : MainFragment() {
(_viewPager.adapter as ArtistViewPagerAdapter).artist = channel (_viewPager.adapter as ArtistViewPagerAdapter).artist = channel
_viewPager.adapter!!.notifyDataSetChanged() _viewPager.adapter!!.notifyDataSetChanged();
val artistThumbnail = channel.getThumbnailOrAlbum();
if(artistThumbnail != null) {
_creatorThumbnail.isVisible = true;
_creatorThumbnail.setThumbnail(channel.getThumbnailOrAlbum(), true, true);
Glide.with(_imageBanner)
.load(artistThumbnail)
.into(_imageBanner);
}
else {
_creatorThumbnail.isVisible = false;
Glide.with(_imageBanner).clear(_imageBanner);
}
this.channel = channel this.channel = channel
} }
@@ -86,6 +86,7 @@ class LibraryFilesFragment : MainFragment() {
} }
fun loadTop() { fun loadTop() {
var initialDirectories = listOf<FileEntry>(); var initialDirectories = listOf<FileEntry>();
var path = "";
if(root == null) { if(root == null) {
initialDirectories = StateLibrary.instance.getFileDirectories(); initialDirectories = StateLibrary.instance.getFileDirectories();
if (initialDirectories.size == 0) { if (initialDirectories.size == 0) {
@@ -109,9 +110,10 @@ class LibraryFilesFragment : MainFragment() {
it.isVisible = false; it.isVisible = false;
} }
initialDirectories = root?.getSubFiles() ?: listOf(); initialDirectories = root?.getSubFiles() ?: listOf();
path = root?.path ?: "";
} }
navStack.clear(); navStack.clear();
val entry = FileStack("", initialDirectories); val entry = FileStack(path, initialDirectories);
navStack.add(entry); navStack.add(entry);
openDirectory(navStack.last()); openDirectory(navStack.last());
fragment.topBar?.let { fragment.topBar?.let {
@@ -2,6 +2,7 @@ package com.futo.platformplayer.fragment.mainactivity.main
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.MediaStore import android.provider.MediaStore
import android.util.AttributeSet import android.util.AttributeSet
@@ -11,11 +12,13 @@ import android.view.ViewGroup
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.collection.emptyLongSet
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.futo.platformplayer.R import com.futo.platformplayer.R
import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.UIDialogs
@@ -34,6 +37,7 @@ import com.futo.platformplayer.views.AnyInsertedAdapterView
import com.futo.platformplayer.views.AnyInsertedAdapterView.Companion.asAnyWithTop import com.futo.platformplayer.views.AnyInsertedAdapterView.Companion.asAnyWithTop
import com.futo.platformplayer.views.AnyInsertedAdapterView.Companion.asAnyWithViews import com.futo.platformplayer.views.AnyInsertedAdapterView.Companion.asAnyWithViews
import com.futo.platformplayer.views.LibrarySection import com.futo.platformplayer.views.LibrarySection
import com.futo.platformplayer.views.NoResultsView
import com.futo.platformplayer.views.adapters.AnyAdapter import com.futo.platformplayer.views.adapters.AnyAdapter
import com.futo.platformplayer.views.adapters.InsertedViewAdapter import com.futo.platformplayer.views.adapters.InsertedViewAdapter
import com.futo.platformplayer.views.adapters.viewholders.AlbumTileViewHolder import com.futo.platformplayer.views.adapters.viewholders.AlbumTileViewHolder
@@ -41,6 +45,9 @@ import com.futo.platformplayer.views.adapters.viewholders.ArtistTileViewHolder
import com.futo.platformplayer.views.adapters.viewholders.FileViewHolder import com.futo.platformplayer.views.adapters.viewholders.FileViewHolder
import com.futo.platformplayer.views.adapters.viewholders.LocalVideoTileViewHolder import com.futo.platformplayer.views.adapters.viewholders.LocalVideoTileViewHolder
import com.futo.platformplayer.views.buttons.BigButton import com.futo.platformplayer.views.buttons.BigButton
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import okhttp3.Dispatcher
class LibraryFragment : MainFragment() { class LibraryFragment : MainFragment() {
@@ -146,11 +153,12 @@ class LibraryFragment : MainFragment() {
var sectionAlbums: LibrarySection; var sectionAlbums: LibrarySection;
var sectionVideos: LibrarySection; var sectionVideos: LibrarySection;
var sectionFiles: LibrarySection; var sectionFiles: LibrarySection;
var noContent: NoResultsView;
//var buttonFiles: BigButton; //var buttonFiles: BigButton;
val recycler: RecyclerView; val recycler: RecyclerView;
val adapterFiles: AnyInsertedAdapterView<FileEntry, FileViewHolder>; var adapterFiles: AnyInsertedAdapterView<FileEntry, FileViewHolder>? = null;
//var metaInfo: TextView; //var metaInfo: TextView;
@@ -186,6 +194,9 @@ class LibraryFragment : MainFragment() {
//buttonFiles = findViewById<BigButton>(R.id.button_files); //buttonFiles = findViewById<BigButton>(R.id.button_files);
//metaInfo = findViewById(R.id.meta_info); //metaInfo = findViewById(R.id.meta_info);
noContent = NoResultsView(context, "No directories", "No directories have been added.\nAdd them using the (+) icon.", -1, listOf());
noContent.isVisible = false;
this.allowMusic = allowMusic ?: false; this.allowMusic = allowMusic ?: false;
this.allowVideo = allowVideo ?: false; this.allowVideo = allowVideo ?: false;
@@ -195,14 +206,6 @@ class LibraryFragment : MainFragment() {
else else
fragment.requestPermissionMusic(); fragment.requestPermissionMusic();
}); });
val adapterArtists = sectionArtists.getAnyAdapter<Artist, ArtistTileViewHolder>({
it.onClick.subscribe {
if(it != null)
fragment.navigate<LibraryArtistFragment>(it);
}
});
val artists = StateLibrary.instance.getArtists(ArtistOrdering.TrackCount);
adapterArtists.setData(artists);
sectionAlbums.setSection("Albums", { sectionAlbums.setSection("Albums", {
if(this.allowMusic) if(this.allowMusic)
@@ -210,14 +213,6 @@ class LibraryFragment : MainFragment() {
else else
fragment.requestPermissionMusic(); fragment.requestPermissionMusic();
}); });
val adapterAlbums = sectionAlbums.getAnyAdapter<Album, AlbumTileViewHolder>({
it.onClick.subscribe {
if(it != null)
fragment.navigate<LibraryAlbumFragment>(it);
}
});
val albums = StateLibrary.instance.getAlbums();
adapterAlbums.setData(albums);
sectionVideos.setSection("Videos", { sectionVideos.setSection("Videos", {
@@ -226,21 +221,118 @@ class LibraryFragment : MainFragment() {
else else
fragment.requestPermissionVideo(); fragment.requestPermissionVideo();
}); });
reloadLibraryUI();
/*
buttonFiles.onClick.subscribe {
fragment.navigate<LibraryFilesFragment>()
} */
//buttonFiles.setButtonEnabled(false);
setMusicPermissions(allowMusic ?: false);
setVideoPermissions(allowVideo ?: false);
}
fun reloadFiles() {
val files = StateLibrary.instance.getFileDirectories();
adapterFiles?.setData(files);
if(files.size == 0) {
noContent.isVisible = true;
}
else
noContent.isVisible = false;
}
fun reloadLibraryUI() {
val adapterAlbums = sectionAlbums.getAnyAdapter<Album, AlbumTileViewHolder>({
it.onClick.subscribe {
if(it != null)
fragment.navigate<LibraryAlbumFragment>(it);
}
});
val adapterArtists = sectionArtists.getAnyAdapter<Artist, ArtistTileViewHolder>({
it.onClick.subscribe {
if(it != null)
fragment.navigate<LibraryArtistFragment>(it);
}
});
val adapterVideos = sectionVideos.getAnyAdapter<IPlatformVideo, LocalVideoTileViewHolder>({ val adapterVideos = sectionVideos.getAnyAdapter<IPlatformVideo, LocalVideoTileViewHolder>({
it.onClick.subscribe { it.onClick.subscribe {
if(it != null) if(it != null)
fragment.navigate<VideoDetailFragment>(it); fragment.navigate<VideoDetailFragment>(it);
} }
}); });
val videos = StateLibrary.instance.getRecentVideos(null, 20);
adapterVideos.setData(videos); if(this.allowMusic) {
val artists = StateLibrary.instance.getArtists(ArtistOrdering.TrackCount);
adapterArtists.setData(artists);
if (artists.size == 0)
sectionArtists.setEmpty(
"No artists",
"No artists were found on your device",
-1
);
else
sectionArtists.clearEmpty();
}
else if(Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
sectionAlbums.isVisible = false;
}
else {
sectionArtists.setEmpty(
"No Music Permissions",
"You have not granted music access permissions to Grayjay",
-1
);
}
if(this.allowMusic) {
val albums = StateLibrary.instance.getAlbums();
adapterAlbums.setData(albums);
if (albums.size == 0)
sectionAlbums.setEmpty("No albums", "No albums were found on your device", -1);
else
sectionAlbums.clearEmpty();
}
else if(Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
sectionArtists.isVisible = false;
}
else {
sectionAlbums.setEmpty(
"No Music Permissions",
"You have not granted music access permissions to Grayjay",
-1
);
}
if(this.allowVideo) {
val videos = StateLibrary.instance.getRecentVideos(null, 20);
adapterVideos.setData(videos);
if (videos.size == 0)
sectionVideos.setEmpty("No videos", "No videos were found on your device", -1);
else
sectionVideos.clearEmpty();
}
else if(Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
sectionVideos.isVisible = false;
}
else {
sectionVideos.setEmpty(
"No Video Permissions",
"You have not granted video access permissions to Grayjay",
-1
);
}
adapterFiles = recycler.asAnyWithViews<FileEntry, FileViewHolder>( adapterFiles = recycler.asAnyWithViews<FileEntry, FileViewHolder>(
arrayListOf( arrayListOf(
sectionArtists, sectionArtists,
sectionAlbums, sectionAlbums,
sectionVideos, sectionVideos,
sectionFiles sectionFiles,
noContent
), ),
arrayListOf(View(context).apply { this.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 20.dp(resources)) }), arrayListOf(View(context).apply { this.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 20.dp(resources)) }),
RecyclerView.VERTICAL, false, { RecyclerView.VERTICAL, false, {
@@ -257,23 +349,8 @@ class LibraryFragment : MainFragment() {
} }
); );
reloadFiles(); reloadFiles();
/*
buttonFiles.onClick.subscribe {
fragment.navigate<LibraryFilesFragment>()
} */
//buttonFiles.setButtonEnabled(false);
setMusicPermissions(allowMusic ?: false);
setVideoPermissions(allowVideo ?: false);
} }
fun reloadFiles() {
val files = StateLibrary.instance.getFileDirectories();
adapterFiles.setData(files);
}
fun setMusicPermissions(access: Boolean) { fun setMusicPermissions(access: Boolean) {
allowMusic = access; allowMusic = access;
sectionAlbums.setContentEmptyMessage(R.drawable.ic_library, "No mediastore permissions"); sectionAlbums.setContentEmptyMessage(R.drawable.ic_library, "No mediastore permissions");
@@ -283,6 +360,10 @@ class LibraryFragment : MainFragment() {
// if(!allowMusic) "You did not give access to local music, so these options are disabled" else null, // 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 // if(!allowVideo) "You did not give access to local videos, so these options are disabled" else null
//).filterNotNull().joinToString("\n"); //).filterNotNull().joinToString("\n");
fragment.lifecycleScope.launch(Dispatchers.Main) {
reloadLibraryUI();
}
} }
fun setVideoPermissions(access: Boolean) { fun setVideoPermissions(access: Boolean) {
allowVideo = access; allowVideo = access;
@@ -291,10 +372,22 @@ class LibraryFragment : MainFragment() {
// if(!allowMusic) "You did not give access to local music, so these options are disabled" else null, // 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 // if(!allowVideo) "You did not give access to local videos, so these options are disabled" else null
//).filterNotNull().joinToString("\n"); //).filterNotNull().joinToString("\n");
// }
fragment.lifecycleScope.launch(Dispatchers.Main) {
reloadLibraryUI();
}
} }
fun onShown() { fun onShown() {
if(didShowAlpha)
return;
didShowAlpha = true;
UIDialogs.appToast("Library is in alpha\nImprovements are coming to local media playback.") UIDialogs.appToast("Library is in alpha\nImprovements are coming to local media playback.")
} }
companion object {
var didShowAlpha: Boolean = false;
}
} }
} }
@@ -1753,7 +1753,9 @@ class VideoDetailView : ConstraintLayout {
} }
} catch (e: Throwable) { } catch (e: Throwable) {
Logger.e(TAG, "Failed to get polycentric likes/dislikes.", e); Logger.e(TAG, "Failed to get polycentric likes/dislikes.", e);
_rating.visibility = View.GONE; fragment.lifecycleScope.launch(Dispatchers.Main) {
_rating.visibility = View.GONE;
}
} }
} }
} }
@@ -68,6 +68,20 @@ class StateApp {
val sessionId = UUID.randomUUID().toString(); val sessionId = UUID.randomUUID().toString();
var airplaneMode: Boolean = false
get(){
return field;
}
private set(value) {
field = value;
}
val airplaneModeChanged = Event1<Boolean>();
fun setAirMode(value: Boolean) {
airplaneMode = value;
airplaneModeChanged.emit(airplaneMode);
}
var privateMode: Boolean = false var privateMode: Boolean = false
get(){ get(){
return field; return field;
@@ -4,6 +4,7 @@ import android.content.ContentUris
import android.content.Intent import android.content.Intent
import android.database.Cursor import android.database.Cursor
import android.net.Uri import android.net.Uri
import android.os.Build
import android.provider.MediaStore import android.provider.MediaStore
import android.provider.MediaStore.Audio.Artists import android.provider.MediaStore.Audio.Artists
import android.webkit.MimeTypeMap import android.webkit.MimeTypeMap
@@ -36,6 +37,8 @@ import java.io.File
import java.time.Instant import java.time.Instant
import java.time.OffsetDateTime import java.time.OffsetDateTime
import java.time.ZoneOffset import java.time.ZoneOffset
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentMap
class StateLibrary { class StateLibrary {
@@ -192,6 +195,8 @@ class StateLibrary {
private var _cacheBucketNames: List<Bucket>? = null; private var _cacheBucketNames: List<Bucket>? = null;
fun getVideoBucketNames(): List<Bucket> { fun getVideoBucketNames(): List<Bucket> {
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU)
return listOf();
if(_cacheBucketNames != null) if(_cacheBucketNames != null)
return _cacheBucketNames ?: listOf(); return _cacheBucketNames ?: listOf();
try { try {
@@ -236,7 +241,6 @@ class StateLibrary {
val PROJECTION_VIDEO = arrayOf( val PROJECTION_VIDEO = arrayOf(
MediaStore.Video.Media._ID, MediaStore.Video.Media._ID,
MediaStore.Video.Media.DISPLAY_NAME, MediaStore.Video.Media.DISPLAY_NAME,
MediaStore.Video.Media.AUTHOR,
MediaStore.Video.Media.DATE_ADDED, MediaStore.Video.Media.DATE_ADDED,
MediaStore.Video.Media.MIME_TYPE, MediaStore.Video.Media.MIME_TYPE,
MediaStore.Video.Media.BUCKET_DISPLAY_NAME MediaStore.Video.Media.BUCKET_DISPLAY_NAME
@@ -406,10 +410,10 @@ class StateLibrary {
fun videoFromCursor(cursor: Cursor): IPlatformVideoDetails { fun videoFromCursor(cursor: Cursor): IPlatformVideoDetails {
val id = cursor.getString(0); val id = cursor.getString(0);
val displayName = cursor.getString(1); val displayName = cursor.getString(1);
val author = cursor.getString(2); val author = null;//cursor.getString(2);
val date = cursor.getLong(3); val date = cursor.getLong(2);
val contentType = cursor.getString(4); val contentType = cursor.getString(3);
val category = cursor.getString(5); val category = cursor.getString(4);
val idLong = id.toLongOrNull(); val idLong = id.toLongOrNull();
val contentUrl = if(idLong != null ) val contentUrl = if(idLong != null )
@@ -486,6 +490,10 @@ class Artist {
return AdhocPager({ listOf() }, getTracksPager(idLong)); return AdhocPager({ listOf() }, getTracksPager(idLong));
} }
fun getThumbnailOrAlbum(): String? {
return thumbnail ?: tryGetArtistThumbnail(id.toLongOrNull());
}
companion object { companion object {
val ID_UNKNOWN = "UNKNOWN"; val ID_UNKNOWN = "UNKNOWN";
val PROJECTION: Array<String> = arrayOf(Artists._ID, val PROJECTION: Array<String> = arrayOf(Artists._ID,
@@ -493,6 +501,20 @@ class Artist {
Artists.NUMBER_OF_TRACKS, Artists.NUMBER_OF_TRACKS,
Artists.NUMBER_OF_ALBUMS); Artists.NUMBER_OF_ALBUMS);
val thumbnailCache = ConcurrentHashMap<Long, String>();
fun tryGetArtistThumbnail(artistId: Long?): String? {
if(artistId == null)
return null;
if(thumbnailCache.containsKey(artistId))
return thumbnailCache.get(artistId);
else {
val album = Album.getArtistAlbumWithThumbnail(artistId);
thumbnailCache.put(artistId, album?.thumbnail ?: "");
return album?.thumbnail;
}
}
fun fromCursor(cursor: Cursor): Artist { fun fromCursor(cursor: Cursor): Artist {
val id = cursor.getString(0); val id = cursor.getString(0);
val artist = cursor.getString(1); val artist = cursor.getString(1);
@@ -538,8 +560,11 @@ class Artist {
cursor.moveToFirst(); cursor.moveToFirst();
val list = mutableListOf<Artist>() val list = mutableListOf<Artist>()
while(!cursor.isAfterLast) { while(!cursor.isAfterLast) {
list.add(fromCursor(cursor)); val artist = fromCursor(cursor);
cursor.moveToNext(); cursor.moveToNext();
if(artist.name == "<unknown>")
continue; //TODO: Better way of detecting unknown?
list.add(artist);
} }
return@use list; return@use list;
} }
@@ -683,6 +708,26 @@ class Album {
return@use list; return@use list;
} }
} }
fun getArtistAlbumWithThumbnail(artistId: Long): Album? {
val resolver = StateApp.instance.contextOrNull?.contentResolver;
if(resolver == null) {
Logger.w(TAG, "Album contentResolver not found");
return null;
}
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 null;
return cursor.use {
cursor.moveToFirst();
while(!cursor.isAfterLast) {
val album = fromCursor(cursor);
if(album.thumbnail != null)
return album
cursor.moveToNext();
}
return@use null;
}
}
} }
} }
@@ -22,13 +22,14 @@ class LibrarySection: ConstraintLayout {
val imageNavigate: ImageView; val imageNavigate: ImageView;
val recycler: RecyclerView; val recycler: RecyclerView;
val noContent: NoResultsView;
constructor(context: Context, attr: AttributeSet? = null) : super(context, attr) { constructor(context: Context, attr: AttributeSet? = null) : super(context, attr) {
inflate(context, R.layout.view_library_section, this); inflate(context, R.layout.view_library_section, this);
textName = findViewById(R.id.text_label) textName = findViewById(R.id.text_label)
imageNavigate = findViewById(R.id.image_nav) imageNavigate = findViewById(R.id.image_nav)
recycler = findViewById(R.id.recycler_collection); recycler = findViewById(R.id.recycler_collection);
noContent = findViewById(R.id.container_no_content);
} }
fun setNavIcon(resId: Int) { fun setNavIcon(resId: Int) {
@@ -46,4 +47,14 @@ class LibrarySection: ConstraintLayout {
textName.text = title; textName.text = title;
imageNavigate.setOnClickListener { onOpen.invoke() }; imageNavigate.setOnClickListener { onOpen.invoke() };
} }
fun setEmpty(title: String, txt: String, iconId: Int) {
noContent.isVisible = true;
recycler.isVisible = false;
noContent.setText(title, txt, iconId);
}
fun clearEmpty() {
noContent.isVisible = false;
recycler.isVisible = true;
}
} }
@@ -1,6 +1,7 @@
package com.futo.platformplayer.views package com.futo.platformplayer.views
import android.content.Context import android.content.Context
import android.util.AttributeSet
import android.view.View import android.view.View
import android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
@@ -15,6 +16,13 @@ class NoResultsView: ConstraintLayout {
val icon: ImageView; val icon: ImageView;
val containerExtraViews: LinearLayout; val containerExtraViews: LinearLayout;
constructor(context: Context, attributes: AttributeSet? = null) : super(context, attributes){
inflate(context, R.layout.view_no_results, this);
textTitle = findViewById(R.id.text_title)
textCentered = findViewById(R.id.text_centered);
icon = findViewById(R.id.icon);
containerExtraViews = findViewById(R.id.container_extra_views);
}
constructor(context: Context, title: String, text: String, iconId: Int, extraViews: List<View>) : super(context) { constructor(context: Context, title: String, text: String, iconId: Int, extraViews: List<View>) : super(context) {
inflate(context, R.layout.view_no_results, this); inflate(context, R.layout.view_no_results, this);
@@ -22,13 +30,21 @@ class NoResultsView: ConstraintLayout {
textCentered = findViewById(R.id.text_centered); textCentered = findViewById(R.id.text_centered);
icon = findViewById(R.id.icon); icon = findViewById(R.id.icon);
containerExtraViews = findViewById(R.id.container_extra_views); containerExtraViews = findViewById(R.id.container_extra_views);
setText(title, text, iconId, extraViews);
}
fun setText(title: String, text: String, iconId: Int = -1, extraViews: List<View>? = null) {
textTitle.text = title; textTitle.text = title;
textCentered.text = text; textCentered.text = text;
icon.setImageResource(iconId);
if(iconId < 0) if(iconId < 0)
icon.visibility = GONE; icon.visibility = GONE;
else
icon.setImageResource(iconId);
for(view in extraViews) if(extraViews != null)
containerExtraViews.addView(view); for(view in extraViews)
containerExtraViews.addView(view);
} }
} }
@@ -3,9 +3,7 @@ package com.futo.platformplayer.views.adapters
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.view.GestureDetector
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
@@ -46,7 +44,7 @@ class CommentViewHolder : ViewHolder {
private val _imageLikeIcon: ImageView; private val _imageLikeIcon: ImageView;
private val _textLikes: TextView; private val _textLikes: TextView;
private val _imageDislikeIcon: ImageView; private val _imageDislikeIcon: ImageView;
private val _imageCopy: ImageView; private val _buttonCopy: PillButton;
private val _textDislikes: TextView; private val _textDislikes: TextView;
private val _buttonReplies: PillButton; private val _buttonReplies: PillButton;
private val _layoutRating: LinearLayout; private val _layoutRating: LinearLayout;
@@ -70,7 +68,7 @@ class CommentViewHolder : ViewHolder {
_textMetadata = itemView.findViewById(R.id.text_metadata); _textMetadata = itemView.findViewById(R.id.text_metadata);
_textBody = itemView.findViewById(R.id.text_body); _textBody = itemView.findViewById(R.id.text_body);
_imageLikeIcon = itemView.findViewById(R.id.image_like_icon); _imageLikeIcon = itemView.findViewById(R.id.image_like_icon);
_imageCopy = itemView.findViewById(R.id.image_copy); _buttonCopy = itemView.findViewById(R.id.image_copy);
_textLikes = itemView.findViewById(R.id.text_likes); _textLikes = itemView.findViewById(R.id.text_likes);
_imageDislikeIcon = itemView.findViewById(R.id.image_dislike_icon); _imageDislikeIcon = itemView.findViewById(R.id.image_dislike_icon);
_textDislikes = itemView.findViewById(R.id.text_dislikes); _textDislikes = itemView.findViewById(R.id.text_dislikes);
@@ -105,7 +103,8 @@ class CommentViewHolder : ViewHolder {
StatePolycentric.instance.updateLikeMap(c.reference, args.hasLiked, args.hasDisliked) StatePolycentric.instance.updateLikeMap(c.reference, args.hasLiked, args.hasDisliked)
}; };
_imageCopy.setOnLongClickListener { _buttonCopy.setTransparant()
_buttonCopy.onClick.subscribe {
val clipboard = viewGroup.context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clipboard = viewGroup.context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val text = comment?.message.orEmpty() val text = comment?.message.orEmpty()
val clip = ClipData.newPlainText("Comment", text) val clip = ClipData.newPlainText("Comment", text)
@@ -37,9 +37,10 @@ class ArtistTileViewHolder(val _viewGroup: ViewGroup) : AnyAdapter.AnyViewHolder
override fun bind(artist: Artist) { override fun bind(artist: Artist) {
_artist = artist; _artist = artist;
_imageThumbnail?.let { _imageThumbnail?.let {
if (artist.thumbnail != null) val thumbnail = artist.getThumbnailOrAlbum();
if (thumbnail != null)
Glide.with(it) Glide.with(it)
.load(artist.thumbnail) .load(thumbnail)
.placeholder(R.drawable.ic_artist) .placeholder(R.drawable.ic_artist)
.into(it) .into(it)
else else
@@ -7,11 +7,15 @@ import android.view.View
import android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import androidx.compose.ui.graphics.Color
import androidx.core.view.isVisible
import com.futo.platformplayer.R import com.futo.platformplayer.R
import com.futo.platformplayer.constructs.Event0 import com.futo.platformplayer.constructs.Event0
import com.futo.platformplayer.dp
import com.futo.platformplayer.views.LoaderView import com.futo.platformplayer.views.LoaderView
class PillButton : LinearLayout { class PillButton : LinearLayout {
val root: LinearLayout;
val icon: ImageView; val icon: ImageView;
val text: TextView; val text: TextView;
val loaderView: LoaderView; val loaderView: LoaderView;
@@ -23,6 +27,7 @@ class PillButton : LinearLayout {
icon = findViewById(R.id.pill_icon); icon = findViewById(R.id.pill_icon);
text = findViewById(R.id.pill_text); text = findViewById(R.id.pill_text);
loaderView = findViewById(R.id.loader) loaderView = findViewById(R.id.loader)
root = findViewById<LinearLayout>(R.id.root);
val attrArr = context.obtainStyledAttributes(attrs, R.styleable.PillButton, 0, 0); val attrArr = context.obtainStyledAttributes(attrs, R.styleable.PillButton, 0, 0);
val attrIconRef = attrArr.getResourceId(R.styleable.PillButton_pillIcon, -1); val attrIconRef = attrArr.getResourceId(R.styleable.PillButton_pillIcon, -1);
@@ -34,6 +39,13 @@ class PillButton : LinearLayout {
val attrText = attrArr.getText(R.styleable.PillButton_pillText) ?: ""; val attrText = attrArr.getText(R.styleable.PillButton_pillText) ?: "";
text.text = attrText; text.text = attrText;
if(text.text.isNullOrBlank()) {
val dp6 = 6.dp(resources);
val dp7 = 7.dp(resources);
val dp12 = 12.dp(resources);
root.setPadding(dp7, dp6, dp7, dp7)
}
findViewById<LinearLayout>(R.id.root).setOnClickListener { findViewById<LinearLayout>(R.id.root).setOnClickListener {
if (_isLoading) { if (_isLoading) {
return@setOnClickListener return@setOnClickListener
@@ -43,6 +55,10 @@ class PillButton : LinearLayout {
}; };
} }
fun setTransparant() {
root.setBackgroundColor(0);
}
fun setLoading(loading: Boolean) { fun setLoading(loading: Boolean) {
if (loading == _isLoading) { if (loading == _isLoading) {
return return
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#CC000000" />
<stroke android:color="#333333" android:width="1dp" />
<corners android:radius="1dp" />
<padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />
</shape>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#00000000" />
<stroke android:color="#000000" android:width="1dp" />
<corners android:radius="50dp" />
<padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />
</shape>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#000000" />
<stroke android:color="#333333" android:width="1dp" />
<corners android:radius="50dp" />
<padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />
</shape>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#2D63ED" />
<stroke android:color="#333333" android:width="1dp" />
<corners android:radius="50dp" />
<padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />
</shape>
+9
View File
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="@android:color/white"
android:pathData="M294.23,860L294.23,779.23L411.15,697.15L411.15,538.54L100,663.46L100,564.23L411.15,345.62L411.15,168.85Q411.15,140.46 431.39,120.23Q451.62,100 480,100Q508.38,100 528.61,120.23Q548.85,140.46 548.85,168.85L548.85,345.62L860,564.23L860,663.46L548.85,538.54L548.85,697.15L665.38,779.23L665.38,860L480,803.84L294.23,860Z"/>
</vector>
+1 -1
View File
@@ -60,7 +60,7 @@
<com.futo.platformplayer.views.others.CreatorThumbnail <com.futo.platformplayer.views.others.CreatorThumbnail
android:id="@+id/creator_thumbnail" android:id="@+id/creator_thumbnail"
android:background="@drawable/rounded_outline" android:background="@drawable/rounded_outline"
android:layout_width="1dp" android:layout_width="35dp"
android:layout_height="35dp" android:layout_height="35dp"
android:contentDescription="@string/cd_creator_thumbnail" android:contentDescription="@string/cd_creator_thumbnail"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
@@ -25,14 +25,95 @@
</FrameLayout> </FrameLayout>
<!--More Menu--> <!--More Menu-->
<LinearLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/more_menu_buttons" android:layout_width="match_parent"
android:layout_width="wrap_content" android:layout_height="match_parent">
android:layout_height="wrap_content"
android:orientation="vertical" <androidx.constraintlayout.widget.ConstraintLayout
android:layout_gravity="bottom|end" android:id="@+id/container_more_options"
android:gravity="end"> android:layout_width="match_parent"
</LinearLayout> android:layout_height="wrap_content"
android:layout_marginBottom="3dp"
android:layout_marginLeft="3dp"
android:layout_marginRight="0dp"
app:layout_constraintBottom_toTopOf="@id/more_menu_buttons"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginTop="10dp"
android:layout_marginBottom="15dp"
android:layout_marginRight="0dp"
android:gravity="center">
<LinearLayout
android:id="@+id/container_toggle_airplane"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="@drawable/background_menu_toggle"
android:layout_marginRight="3dp"
android:layout_marginLeft="3dp"
android:gravity="center">
<ImageView
android:layout_width="30dp"
android:layout_height="30dp"
android:src="@drawable/ic_flight" />
</LinearLayout>
<LinearLayout
android:id="@+id/container_toggle_privacy"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="@drawable/background_menu_toggle"
android:layout_marginRight="3dp"
android:layout_marginLeft="3dp"
android:gravity="center">
<ImageView
android:layout_width="30dp"
android:layout_height="30dp"
android:src="@drawable/ic_disabled_visible" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/button_close"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginRight="0dp"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginTop="10dp"
android:gravity="center">
<ImageView
android:layout_width="30dp"
android:layout_height="30dp"
android:src="@drawable/ic_close" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/more_menu_buttons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="bottom|end"
android:layout_marginTop="10dp"
android:layout_marginBottom="5dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:gravity="end">
</androidx.recyclerview.widget.RecyclerView>
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout> </FrameLayout>
<LinearLayout <LinearLayout
+10 -11
View File
@@ -96,16 +96,6 @@
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:gravity="center_vertical"> android:gravity="center_vertical">
<ImageView
android:id="@+id/image_copy"
android:layout_width="18dp"
android:layout_height="18dp"
android:contentDescription="@string/copy"
app:srcCompat="@drawable/ic_copy"
app:tint="@color/white"
android:background="@drawable/background_pill"
android:layout_marginStart="9dp" />
<com.futo.platformplayer.views.pills.PillRatingLikesDislikes <com.futo.platformplayer.views.pills.PillRatingLikesDislikes
android:id="@+id/rating" android:id="@+id/rating"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@@ -160,6 +150,15 @@
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="13dp" /> android:textSize="13dp" />
</LinearLayout> </LinearLayout>
<com.futo.platformplayer.views.pills.PillButton
android:id="@+id/image_copy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:pillIcon="@drawable/ic_copy"
app:pillText=""
android:layout_marginStart="15dp"
android:layout_marginEnd="0dp"/>
<com.futo.platformplayer.views.pills.PillButton <com.futo.platformplayer.views.pills.PillButton
android:id="@+id/button_replies" android:id="@+id/button_replies"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@@ -167,7 +166,7 @@
android:contentDescription="@string/cd_button_replies" android:contentDescription="@string/cd_button_replies"
app:pillIcon="@drawable/ic_forum" app:pillIcon="@drawable/ic_forum"
app:pillText="55 Replies" app:pillText="55 Replies"
android:layout_marginStart="15dp" /> android:layout_marginStart="2dp" />
<Space android:layout_width="0dp" <Space android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
@@ -0,0 +1,51 @@
<?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="86dp"
android:layout_height="146dp"
android:layout_marginTop="3dp"
android:layout_marginBottom="3dp"
android:layout_marginLeft="3dp"
android:layout_marginRight="3dp"
android:background="@drawable/background_menu"
android:id="@+id/root"
android:clickable="true">
<ImageView
android:id="@+id/image_icon"
android:layout_height="50dp"
android:layout_width="50dp"
android:scaleType="centerCrop"
app:layout_constraintDimensionRatio="H,1,1"
app:shapeAppearanceOverlay="@style/roundedCorners_4dp"
app:srcCompat="@drawable/unknown_music"
android:background="@drawable/video_thumbnail_outline"
android:layout_marginTop="12dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
<!--TODO: Fix word wrapping with autosize-->
<TextView
android:id="@+id/text_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textSize="11dp"
android:textColor="@color/white"
android:fontFamily="@font/inter_medium"
tools:text="The Beetles"
android:maxLines="2"
android:gravity="center"
app:layout_constraintLeft_toRightOf="@id/image_icon"
app:layout_constraintTop_toBottomOf="@id/image_icon"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginTop="0dp"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -41,6 +41,16 @@
android:layout_marginTop="10dp" android:layout_marginTop="10dp"
android:orientation="horizontal" /> android:orientation="horizontal" />
<com.futo.platformplayer.views.NoResultsView
android:id="@+id/container_no_content"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
app:layout_constraintTop_toBottomOf="@id/text_label"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
<!-- <!--
<ImageButton <ImageButton
android:id="@+id/button_play" android:id="@+id/button_play"