From 20eb53fc38f8e363b53e8394a82a4b29ac08b94c Mon Sep 17 00:00:00 2001 From: Kelvin K Date: Fri, 21 Nov 2025 19:27:28 +0100 Subject: [PATCH 1/8] New menu system --- .../bottombar/MenuBottomBarFragment.kt | 158 +++++++++++++++++- .../drawable/background_menu_round_4dp.xml | 7 + .../layout/fragment_overview_bottom_bar.xml | 67 +++++++- app/src/main/res/layout/list_menu_tile.xml | 48 ++++++ 4 files changed, 267 insertions(+), 13 deletions(-) create mode 100644 app/src/main/res/drawable/background_menu_round_4dp.xml create mode 100644 app/src/main/res/layout/list_menu_tile.xml diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt index 43f16a97..26317fe3 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt @@ -13,13 +13,18 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.* +import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.animation.doOnEnd +import androidx.core.view.updateLayoutParams import androidx.lifecycle.lifecycleScope 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.Settings import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.activities.MainActivity +import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.dp import com.futo.platformplayer.fragment.mainactivity.MainActivityFragment import com.futo.platformplayer.fragment.mainactivity.main.* @@ -27,6 +32,10 @@ import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.states.StatePayment 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.launch import kotlin.math.floor @@ -69,9 +78,15 @@ class MenuBottomBarFragment : MainActivityFragment() { private val _inflater: LayoutInflater; private val _subscribedActivity: MainActivity?; + private val _containerMoreHeader: ConstraintLayout; + private val _toggleAirplaneMode: RoundButton; + private val _togglePrivacy: RoundButton; + private var _overlayMore: FrameLayout; private var _overlayMoreBackground: FrameLayout; - private var _layoutMoreButtons: LinearLayout; + private var _layoutMoreButtons: RecyclerView; + private val _layoutMoreButtonItems = arrayListOf(); + private var _layoutMoreButtonsAdapter: AnyAdapterView; private var _layoutBottomBarButtons: LinearLayout; private var _moreVisible = false; @@ -90,10 +105,55 @@ class MenuBottomBarFragment : MainActivityFragment() { _inflater = inflater; inflater.inflate(R.layout.fragment_overview_bottom_bar, this); + _containerMoreHeader = findViewById(R.id.container_more_options); + _toggleAirplaneMode = findViewById(R.id.toggle_airplane); + _togglePrivacy = findViewById(R.id.toggle_privacy); + + _toggleAirplaneMode.visibility = GONE; + _toggleAirplaneMode.icon.setImageResource(R.drawable.ic_library); + _toggleAirplaneMode.onClick.subscribe { + if(StateApp.instance.privateMode) { + _togglePrivacy.icon.setImageResource(R.drawable.ic_disabled_visible); + //StateApp.instance.setPrivacyMode(false); + UIDialogs.appToast("Airplane mode disabled"); + } + else { + _togglePrivacy.icon.setImageResource(R.drawable.ic_disabled_visible_purple); + //StateApp.instance.setPrivacyMode(true); + UIDialogs.appToast("Airplane mode enabled"); + } + } + _togglePrivacy.icon.setImageResource(R.drawable.ic_disabled_visible) + _togglePrivacy.onClick.subscribe { + if(StateApp.instance.privateMode) { + _togglePrivacy.icon.setImageResource(R.drawable.ic_disabled_visible); + StateApp.instance.setPrivacyMode(false); + UIDialogs.appToast("Privacy mode disabled"); + } + else { + _togglePrivacy.icon.setImageResource(R.drawable.ic_disabled_visible_purple); + StateApp.instance.setPrivacyMode(true); + UIDialogs.appToast("Privacy mode enabled"); + } + } + _overlayMore = findViewById(R.id.more_overlay); _overlayMoreBackground = findViewById(R.id.more_overlay_background); _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(_layoutMoreButtonItems, + RecyclerView.VERTICAL, false, { button -> + button.setAutoSize(totalWidthDp); + button.parentFragment = this@MenuBottomBarView._fragment; + button.onClick.subscribe { + setMoreVisible(false); + } + }) + val layoutManager = GridLayoutManager(context, columns); + _layoutMoreButtons.layoutManager = layoutManager; _overlayMoreBackground.setOnClickListener { setMoreVisible(false); }; @@ -120,6 +180,8 @@ class MenuBottomBarFragment : MainActivityFragment() { } private fun setMoreVisible(visible: Boolean) { + + //TODO: issues with these bools if (_moreVisibleAnimating) { return } @@ -128,9 +190,12 @@ class MenuBottomBarFragment : MainActivityFragment() { return } + +/* val height = _moreButtons.firstOrNull()?.let { it.height.toFloat() + (it.layoutParams as MarginLayoutParams).bottomMargin } ?: return + */ _moreVisibleAnimating = true val moreOverlayBackground = _overlayMoreBackground @@ -142,14 +207,17 @@ class MenuBottomBarFragment : MainActivityFragment() { moreOverlay.visibility = VISIBLE val animations = arrayListOf() 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 { animations.add(ObjectAnimator.ofFloat(it, "alpha", 0.5f, 1.0f) .setDuration(duration)); } + animations.add(ObjectAnimator.ofFloat(_layoutMoreButtons, "translationY", resources.displayMetrics.heightPixels.toFloat(), 0.0f).setDuration(duration)) for ((index, button) in _moreButtons.withIndex()) { 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() @@ -164,14 +232,21 @@ class MenuBottomBarFragment : MainActivityFragment() { animations .add(ObjectAnimator.ofFloat(moreOverlayBackground, "alpha", 1.0f, 0.0f) .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 { animations.add(ObjectAnimator.ofFloat(it, "alpha", 1.0f, 0.5f) .setDuration(duration)); } + animations.add(ObjectAnimator.ofFloat(_layoutMoreButtons, "translationY", 0.0f, resources.displayMetrics.heightPixels.toFloat()).setDuration(duration)) for ((index, button) in _moreButtons.withIndex()) { 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() @@ -183,11 +258,12 @@ class MenuBottomBarFragment : MainActivityFragment() { animatorSet.playTogether(animations) animatorSet.start() } + } private fun updateBottomMenuButtons(buttons: MutableList, hasMore: Boolean) { 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(); @@ -252,7 +328,9 @@ class MenuBottomBarFragment : MainActivityFragment() { insertedButtons++; } + val newButtons = mutableListOf(); for (data in buttons) { + /* val button = MenuButton(context, data, _fragment, true); button.setOnClickListener { updateMenuIcons() @@ -262,7 +340,12 @@ class MenuBottomBarFragment : MainActivityFragment() { _moreButtons.add(button); _layoutMoreButtons.addView(button); + */ + val buttonItem = MenuButtonItem(data); + newButtons.add(buttonItem); } + _layoutMoreButtonsAdapter.setData(newButtons); + _layoutMoreButtonsAdapter.notifyContentChanged(); } private fun updateMenuIcons() { @@ -350,6 +433,71 @@ class MenuBottomBarFragment : MainActivityFragment() { } + class MenuButtonItem(val def: ButtonDefinition); + class MenuButtonItemViewHolder(private val _viewGroup: ViewGroup): AnyAdapter.AnyViewHolder( + LayoutInflater.from(_viewGroup.context).inflate(R.layout.list_menu_tile, + _viewGroup, false)) { + + val onClick = Event1(); + + 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 - 12).dp(_viewGroup.context.resources); + this.height = (dp - 12).dp(_viewGroup.context.resources); + } + imageIcon.updateLayoutParams { + this.width = (dp - 46).dp(_viewGroup.context.resources); + this.height = (dp - 46).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 { val definition: ButtonDefinition; diff --git a/app/src/main/res/drawable/background_menu_round_4dp.xml b/app/src/main/res/drawable/background_menu_round_4dp.xml new file mode 100644 index 00000000..04a3d7a8 --- /dev/null +++ b/app/src/main/res/drawable/background_menu_round_4dp.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_overview_bottom_bar.xml b/app/src/main/res/layout/fragment_overview_bottom_bar.xml index 11c33b2c..522cde71 100644 --- a/app/src/main/res/layout/fragment_overview_bottom_bar.xml +++ b/app/src/main/res/layout/fragment_overview_bottom_bar.xml @@ -25,14 +25,65 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 98d008ef6ca4aafbb3736e9dbe731646b9525aba Mon Sep 17 00:00:00 2001 From: Kelvin K Date: Sat, 22 Nov 2025 01:02:20 +0100 Subject: [PATCH 2/8] new menu polished, toggles, etc --- .../bottombar/MenuBottomBarFragment.kt | 56 +++++++++------ .../futo/platformplayer/states/StateApp.kt | 14 ++++ ...menu_round_4dp.xml => background_menu.xml} | 6 +- .../res/drawable/background_menu_close.xml | 7 ++ .../res/drawable/background_menu_toggle.xml | 7 ++ .../background_menu_toggle_active.xml | 7 ++ app/src/main/res/drawable/ic_flight.xml | 9 +++ .../layout/fragment_overview_bottom_bar.xml | 70 +++++++++++++------ app/src/main/res/layout/list_menu_tile.xml | 29 ++++---- 9 files changed, 148 insertions(+), 57 deletions(-) rename app/src/main/res/drawable/{background_menu_round_4dp.xml => background_menu.xml} (60%) create mode 100644 app/src/main/res/drawable/background_menu_close.xml create mode 100644 app/src/main/res/drawable/background_menu_toggle.xml create mode 100644 app/src/main/res/drawable/background_menu_toggle_active.xml create mode 100644 app/src/main/res/drawable/ic_flight.xml diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt index 26317fe3..9673ac6e 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt @@ -79,8 +79,8 @@ class MenuBottomBarFragment : MainActivityFragment() { private val _subscribedActivity: MainActivity?; private val _containerMoreHeader: ConstraintLayout; - private val _toggleAirplaneMode: RoundButton; - private val _togglePrivacy: RoundButton; + private val _toggleAirplaneMode: LinearLayout; + private val _togglePrivacy: LinearLayout; private var _overlayMore: FrameLayout; private var _overlayMoreBackground: FrameLayout; @@ -106,32 +106,46 @@ class MenuBottomBarFragment : MainActivityFragment() { inflater.inflate(R.layout.fragment_overview_bottom_bar, this); _containerMoreHeader = findViewById(R.id.container_more_options); - _toggleAirplaneMode = findViewById(R.id.toggle_airplane); - _togglePrivacy = findViewById(R.id.toggle_privacy); + _toggleAirplaneMode = findViewById(R.id.container_toggle_airplane); + _togglePrivacy = findViewById(R.id.container_toggle_privacy); - _toggleAirplaneMode.visibility = GONE; - _toggleAirplaneMode.icon.setImageResource(R.drawable.ic_library); - _toggleAirplaneMode.onClick.subscribe { - if(StateApp.instance.privateMode) { - _togglePrivacy.icon.setImageResource(R.drawable.ic_disabled_visible); - //StateApp.instance.setPrivacyMode(false); + 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 { - _togglePrivacy.icon.setImageResource(R.drawable.ic_disabled_visible_purple); - //StateApp.instance.setPrivacyMode(true); + StateApp.instance.setAirMode(true); UIDialogs.appToast("Airplane mode enabled"); } } - _togglePrivacy.icon.setImageResource(R.drawable.ic_disabled_visible) - _togglePrivacy.onClick.subscribe { + + 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) { - _togglePrivacy.icon.setImageResource(R.drawable.ic_disabled_visible); StateApp.instance.setPrivacyMode(false); UIDialogs.appToast("Privacy mode disabled"); } else { - _togglePrivacy.icon.setImageResource(R.drawable.ic_disabled_visible_purple); StateApp.instance.setPrivacyMode(true); UIDialogs.appToast("Privacy mode enabled"); } @@ -152,7 +166,7 @@ class MenuBottomBarFragment : MainActivityFragment() { setMoreVisible(false); } }) - val layoutManager = GridLayoutManager(context, columns); + val layoutManager = GridLayoutManager(context, columns, GridLayoutManager.VERTICAL, true); _layoutMoreButtons.layoutManager = layoutManager; _overlayMoreBackground.setOnClickListener { setMoreVisible(false); }; @@ -472,12 +486,12 @@ class MenuBottomBarFragment : MainActivityFragment() { fun setWidth(dp: Int) { root.updateLayoutParams { - this.width = (dp - 12).dp(_viewGroup.context.resources); - this.height = (dp - 12).dp(_viewGroup.context.resources); + this.width = (dp - 6).dp(_viewGroup.context.resources); + this.height = (dp - 6).dp(_viewGroup.context.resources); } imageIcon.updateLayoutParams { - this.width = (dp - 46).dp(_viewGroup.context.resources); - this.height = (dp - 46).dp(_viewGroup.context.resources); + this.width = (dp - 54).dp(_viewGroup.context.resources); + this.height = (dp - 54).dp(_viewGroup.context.resources); } } diff --git a/app/src/main/java/com/futo/platformplayer/states/StateApp.kt b/app/src/main/java/com/futo/platformplayer/states/StateApp.kt index 659077d2..f22f1835 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateApp.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateApp.kt @@ -68,6 +68,20 @@ class StateApp { val sessionId = UUID.randomUUID().toString(); + + var airplaneMode: Boolean = false + get(){ + return field; + } + private set(value) { + field = value; + } + val airplaneModeChanged = Event1(); + fun setAirMode(value: Boolean) { + airplaneMode = value; + airplaneModeChanged.emit(airplaneMode); + } + var privateMode: Boolean = false get(){ return field; diff --git a/app/src/main/res/drawable/background_menu_round_4dp.xml b/app/src/main/res/drawable/background_menu.xml similarity index 60% rename from app/src/main/res/drawable/background_menu_round_4dp.xml rename to app/src/main/res/drawable/background_menu.xml index 04a3d7a8..f35f3b6e 100644 --- a/app/src/main/res/drawable/background_menu_round_4dp.xml +++ b/app/src/main/res/drawable/background_menu.xml @@ -1,7 +1,7 @@ - - - + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_menu_close.xml b/app/src/main/res/drawable/background_menu_close.xml new file mode 100644 index 00000000..d5689eb9 --- /dev/null +++ b/app/src/main/res/drawable/background_menu_close.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_menu_toggle.xml b/app/src/main/res/drawable/background_menu_toggle.xml new file mode 100644 index 00000000..91c9cd68 --- /dev/null +++ b/app/src/main/res/drawable/background_menu_toggle.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_menu_toggle_active.xml b/app/src/main/res/drawable/background_menu_toggle_active.xml new file mode 100644 index 00000000..32e54aa9 --- /dev/null +++ b/app/src/main/res/drawable/background_menu_toggle_active.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_flight.xml b/app/src/main/res/drawable/ic_flight.xml new file mode 100644 index 00000000..99f2fa1a --- /dev/null +++ b/app/src/main/res/drawable/ic_flight.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/fragment_overview_bottom_bar.xml b/app/src/main/res/layout/fragment_overview_bottom_bar.xml index 522cde71..150f03e0 100644 --- a/app/src/main/res/layout/fragment_overview_bottom_bar.xml +++ b/app/src/main/res/layout/fragment_overview_bottom_bar.xml @@ -33,36 +33,66 @@ android:id="@+id/container_more_options" android:layout_width="match_parent" android:layout_height="wrap_content" - app:layout_constraintTop_toTopOf="parent" + 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"> + android:layout_marginRight="0dp" + android:gravity="center"> - - - - - + + + + + + + + + + + @@ -71,11 +101,11 @@ + \ No newline at end of file From ce2029774e8fe8d4a5df559c774bb96d360f61af Mon Sep 17 00:00:00 2001 From: Kelvin K Date: Mon, 24 Nov 2025 21:57:03 +0100 Subject: [PATCH 3/8] Deal with older Android versions --- .../bottombar/MenuBottomBarFragment.kt | 10 +- .../mainactivity/main/LibraryFilesFragment.kt | 4 +- .../mainactivity/main/LibraryFragment.kt | 149 ++++++++++++++---- .../platformplayer/states/StateLibrary.kt | 12 +- .../platformplayer/views/LibrarySection.kt | 9 +- .../platformplayer/views/NoResultsView.kt | 22 ++- .../layout/fragment_overview_bottom_bar.xml | 2 +- .../main/res/layout/view_library_section.xml | 10 ++ 8 files changed, 170 insertions(+), 48 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt index 9673ac6e..402625f8 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt @@ -8,6 +8,7 @@ import android.app.Activity import android.content.Context import android.content.Intent import android.content.res.Configuration +import android.os.Build import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -15,6 +16,7 @@ import android.view.ViewGroup import android.widget.* import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.animation.doOnEnd +import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.lifecycle.lifecycleScope import androidx.media3.common.util.UnstableApi @@ -109,6 +111,8 @@ class MenuBottomBarFragment : MainActivityFragment() { _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) @@ -575,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(withHistory = false) }), - ButtonDefinition(12, R.drawable.ic_library, R.drawable.ic_library, R.string.library, canToggle = false, { it.currentMain is LibraryFragment }, { it.navigate(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(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(withHistory = false) }), ButtonDefinition(3, R.drawable.ic_sources, R.drawable.ic_sources_filled, R.string.sources, canToggle = false, { it.currentMain is SourcesFragment }, { it.navigate(withHistory = false) }), ButtonDefinition(4, R.drawable.ic_playlist, R.drawable.ic_playlist_filled, R.string.playlists, canToggle = false, { it.currentMain is PlaylistsFragment }, { it.navigate(withHistory = false) }), @@ -613,7 +619,7 @@ class MenuBottomBarFragment : MainActivityFragment() { //96 is reserved for privacy button //98 is reserved for buy button //99 is reserved for more button - ); + ).filterNotNull(); } data class ButtonDefinition( diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryFilesFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryFilesFragment.kt index 67c5bccd..fcef3098 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryFilesFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryFilesFragment.kt @@ -86,6 +86,7 @@ class LibraryFilesFragment : MainFragment() { } fun loadTop() { var initialDirectories = listOf(); + var path = ""; if(root == null) { initialDirectories = StateLibrary.instance.getFileDirectories(); if (initialDirectories.size == 0) { @@ -109,9 +110,10 @@ class LibraryFilesFragment : MainFragment() { it.isVisible = false; } initialDirectories = root?.getSubFiles() ?: listOf(); + path = root?.path ?: ""; } navStack.clear(); - val entry = FileStack("", initialDirectories); + val entry = FileStack(path, initialDirectories); navStack.add(entry); openDirectory(navStack.last()); fragment.topBar?.let { diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryFragment.kt index 1d58331a..0c866944 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryFragment.kt @@ -2,6 +2,7 @@ package com.futo.platformplayer.fragment.mainactivity.main import android.content.Intent import android.content.pm.PackageManager +import android.os.Build import android.os.Bundle import android.provider.MediaStore import android.util.AttributeSet @@ -16,6 +17,7 @@ import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.RecyclerView import com.futo.platformplayer.R import com.futo.platformplayer.UIDialogs @@ -34,6 +36,7 @@ import com.futo.platformplayer.views.AnyInsertedAdapterView import com.futo.platformplayer.views.AnyInsertedAdapterView.Companion.asAnyWithTop import com.futo.platformplayer.views.AnyInsertedAdapterView.Companion.asAnyWithViews 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.InsertedViewAdapter import com.futo.platformplayer.views.adapters.viewholders.AlbumTileViewHolder @@ -41,6 +44,9 @@ import com.futo.platformplayer.views.adapters.viewholders.ArtistTileViewHolder import com.futo.platformplayer.views.adapters.viewholders.FileViewHolder import com.futo.platformplayer.views.adapters.viewholders.LocalVideoTileViewHolder import com.futo.platformplayer.views.buttons.BigButton +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import okhttp3.Dispatcher class LibraryFragment : MainFragment() { @@ -146,11 +152,12 @@ class LibraryFragment : MainFragment() { var sectionAlbums: LibrarySection; var sectionVideos: LibrarySection; var sectionFiles: LibrarySection; + var noContent: NoResultsView; //var buttonFiles: BigButton; val recycler: RecyclerView; - val adapterFiles: AnyInsertedAdapterView; + var adapterFiles: AnyInsertedAdapterView? = null; //var metaInfo: TextView; @@ -186,6 +193,9 @@ class LibraryFragment : MainFragment() { //buttonFiles = findViewById(R.id.button_files); //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.allowVideo = allowVideo ?: false; @@ -195,14 +205,6 @@ class LibraryFragment : MainFragment() { else fragment.requestPermissionMusic(); }); - val adapterArtists = sectionArtists.getAnyAdapter({ - it.onClick.subscribe { - if(it != null) - fragment.navigate(it); - } - }); - val artists = StateLibrary.instance.getArtists(ArtistOrdering.TrackCount); - adapterArtists.setData(artists); sectionAlbums.setSection("Albums", { if(this.allowMusic) @@ -210,14 +212,6 @@ class LibraryFragment : MainFragment() { else fragment.requestPermissionMusic(); }); - val adapterAlbums = sectionAlbums.getAnyAdapter({ - it.onClick.subscribe { - if(it != null) - fragment.navigate(it); - } - }); - val albums = StateLibrary.instance.getAlbums(); - adapterAlbums.setData(albums); sectionVideos.setSection("Videos", { @@ -226,21 +220,112 @@ class LibraryFragment : MainFragment() { else fragment.requestPermissionVideo(); }); + + reloadLibraryUI(); + + + /* + buttonFiles.onClick.subscribe { + fragment.navigate() + } */ + //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({ + it.onClick.subscribe { + if(it != null) + fragment.navigate(it); + } + }); + val adapterArtists = sectionArtists.getAnyAdapter({ + it.onClick.subscribe { + if(it != null) + fragment.navigate(it); + } + }); val adapterVideos = sectionVideos.getAnyAdapter({ it.onClick.subscribe { if(it != null) fragment.navigate(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 if(Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) { + 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 if(Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) { + 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 if(Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) { + sectionVideos.isVisible = false; + } + else { + sectionVideos.setEmpty( + "No Video Permissions", + "You have not granted video access permissions to Grayjay", + -1 + ); + } adapterFiles = recycler.asAnyWithViews( arrayListOf( sectionArtists, sectionAlbums, sectionVideos, - sectionFiles + sectionFiles, + noContent ), arrayListOf(View(context).apply { this.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 20.dp(resources)) }), RecyclerView.VERTICAL, false, { @@ -257,23 +342,8 @@ class LibraryFragment : MainFragment() { } ); reloadFiles(); - - - /* - buttonFiles.onClick.subscribe { - fragment.navigate() - } */ - //buttonFiles.setButtonEnabled(false); - setMusicPermissions(allowMusic ?: false); - setVideoPermissions(allowVideo ?: false); } - fun reloadFiles() { - val files = StateLibrary.instance.getFileDirectories(); - adapterFiles.setData(files); - } - - fun setMusicPermissions(access: Boolean) { allowMusic = access; sectionAlbums.setContentEmptyMessage(R.drawable.ic_library, "No mediastore permissions"); @@ -283,6 +353,10 @@ class LibraryFragment : MainFragment() { // 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"); + + fragment.lifecycleScope.launch(Dispatchers.Main) { + reloadLibraryUI(); + } } fun setVideoPermissions(access: Boolean) { allowVideo = access; @@ -291,6 +365,11 @@ class LibraryFragment : MainFragment() { // 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"); + // } + + fragment.lifecycleScope.launch(Dispatchers.Main) { + reloadLibraryUI(); + } } fun onShown() { diff --git a/app/src/main/java/com/futo/platformplayer/states/StateLibrary.kt b/app/src/main/java/com/futo/platformplayer/states/StateLibrary.kt index d7680452..652834f3 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateLibrary.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateLibrary.kt @@ -4,6 +4,7 @@ import android.content.ContentUris import android.content.Intent import android.database.Cursor import android.net.Uri +import android.os.Build import android.provider.MediaStore import android.provider.MediaStore.Audio.Artists import android.webkit.MimeTypeMap @@ -192,6 +193,8 @@ class StateLibrary { private var _cacheBucketNames: List? = null; fun getVideoBucketNames(): List { + if(Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) + return listOf(); if(_cacheBucketNames != null) return _cacheBucketNames ?: listOf(); try { @@ -236,7 +239,6 @@ class StateLibrary { val PROJECTION_VIDEO = arrayOf( MediaStore.Video.Media._ID, MediaStore.Video.Media.DISPLAY_NAME, - MediaStore.Video.Media.AUTHOR, MediaStore.Video.Media.DATE_ADDED, MediaStore.Video.Media.MIME_TYPE, MediaStore.Video.Media.BUCKET_DISPLAY_NAME @@ -406,10 +408,10 @@ class StateLibrary { fun videoFromCursor(cursor: Cursor): IPlatformVideoDetails { val id = cursor.getString(0); val displayName = cursor.getString(1); - val author = cursor.getString(2); - val date = cursor.getLong(3); - val contentType = cursor.getString(4); - val category = cursor.getString(5); + val author = null;//cursor.getString(2); + val date = cursor.getLong(2); + val contentType = cursor.getString(3); + val category = cursor.getString(4); val idLong = id.toLongOrNull(); val contentUrl = if(idLong != null ) diff --git a/app/src/main/java/com/futo/platformplayer/views/LibrarySection.kt b/app/src/main/java/com/futo/platformplayer/views/LibrarySection.kt index 176175be..f9af189c 100644 --- a/app/src/main/java/com/futo/platformplayer/views/LibrarySection.kt +++ b/app/src/main/java/com/futo/platformplayer/views/LibrarySection.kt @@ -22,13 +22,14 @@ class LibrarySection: ConstraintLayout { val imageNavigate: ImageView; val recycler: RecyclerView; + val noContent: NoResultsView; constructor(context: Context, attr: AttributeSet? = null) : super(context, attr) { inflate(context, R.layout.view_library_section, this); textName = findViewById(R.id.text_label) imageNavigate = findViewById(R.id.image_nav) recycler = findViewById(R.id.recycler_collection); - + noContent = findViewById(R.id.container_no_content); } fun setNavIcon(resId: Int) { @@ -46,4 +47,10 @@ class LibrarySection: ConstraintLayout { textName.text = title; imageNavigate.setOnClickListener { onOpen.invoke() }; } + + fun setEmpty(title: String, txt: String, iconId: Int) { + noContent.isVisible = true; + recycler.isVisible = false; + noContent.setText(title, txt, iconId); + } } \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/NoResultsView.kt b/app/src/main/java/com/futo/platformplayer/views/NoResultsView.kt index d3e710f5..5903f69a 100644 --- a/app/src/main/java/com/futo/platformplayer/views/NoResultsView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/NoResultsView.kt @@ -1,6 +1,7 @@ package com.futo.platformplayer.views import android.content.Context +import android.util.AttributeSet import android.view.View import android.widget.ImageView import android.widget.LinearLayout @@ -15,6 +16,13 @@ class NoResultsView: ConstraintLayout { val icon: ImageView; 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) : super(context) { inflate(context, R.layout.view_no_results, this); @@ -22,13 +30,21 @@ class NoResultsView: ConstraintLayout { textCentered = findViewById(R.id.text_centered); icon = findViewById(R.id.icon); containerExtraViews = findViewById(R.id.container_extra_views); + + setText(title, text, iconId, extraViews); + } + + + fun setText(title: String, text: String, iconId: Int = -1, extraViews: List? = null) { textTitle.text = title; textCentered.text = text; - icon.setImageResource(iconId); if(iconId < 0) icon.visibility = GONE; + else + icon.setImageResource(iconId); - for(view in extraViews) - containerExtraViews.addView(view); + if(extraViews != null) + for(view in extraViews) + containerExtraViews.addView(view); } } \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_overview_bottom_bar.xml b/app/src/main/res/layout/fragment_overview_bottom_bar.xml index 150f03e0..b8ad4e45 100644 --- a/app/src/main/res/layout/fragment_overview_bottom_bar.xml +++ b/app/src/main/res/layout/fragment_overview_bottom_bar.xml @@ -75,7 +75,7 @@ + android:src="@drawable/ic_disabled_visible" /> diff --git a/app/src/main/res/layout/view_library_section.xml b/app/src/main/res/layout/view_library_section.xml index b2c26dd3..e849b980 100644 --- a/app/src/main/res/layout/view_library_section.xml +++ b/app/src/main/res/layout/view_library_section.xml @@ -41,6 +41,16 @@ android:layout_marginTop="10dp" android:orientation="horizontal" /> + +