From 20eb53fc38f8e363b53e8394a82a4b29ac08b94c Mon Sep 17 00:00:00 2001 From: Kelvin K Date: Fri, 21 Nov 2025 19:27:28 +0100 Subject: [PATCH 1/2] 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/2] 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