Load settings in LongPressMenu too

This commit is contained in:
Stypox 2026-01-06 23:39:12 +01:00
parent 85cb372f5f
commit c62004d903
No known key found for this signature in database
GPG Key ID: 4BDF1B40A49FDD23
4 changed files with 98 additions and 35 deletions

View File

@ -1,9 +0,0 @@
package org.schabi.newpipe.ktx
fun <A> MutableList<A>.popFirst(filter: (A) -> Boolean): A? {
val i = indexOfFirst(filter)
if (i < 0) {
return null
}
return removeAt(i)
}

View File

@ -1,6 +1,7 @@
package org.schabi.newpipe.ui.components.menu
import android.content.Context
import android.net.Uri
import android.widget.Toast
import androidx.annotation.StringRes
import androidx.compose.material.icons.Icons
@ -21,6 +22,7 @@ import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material.icons.filled.QueuePlayNext
import androidx.compose.material.icons.filled.Share
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.core.net.toUri
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import org.schabi.newpipe.R
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry
@ -49,6 +51,7 @@ import org.schabi.newpipe.ui.components.menu.icons.PlayFromHere
import org.schabi.newpipe.ui.components.menu.icons.PopupFromHere
import org.schabi.newpipe.util.NavigationHelper
import org.schabi.newpipe.util.SparseItemUtil
import org.schabi.newpipe.util.external_communication.KoreUtils
import org.schabi.newpipe.util.external_communication.ShareUtils
data class LongPressAction(
@ -228,13 +231,10 @@ data class LongPressAction(
.observeOn(AndroidSchedulers.mainThread())
.subscribe()
},
Type.PlayWithKodi.buildAction { context ->
KoreUtils.playWithKore(context, item.url.toUri())
},
)
/* TODO handle kodi
+ if (isKodiEnabled) listOf(
Type.PlayWithKodi.buildAction { context ->
KoreUtils.playWithKore(context, Uri.parse(item.url))
},
) else listOf()*/
}
@JvmStatic

View File

@ -43,6 +43,8 @@ import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@ -70,10 +72,10 @@ import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.lifecycle.viewmodel.compose.viewModel
import coil3.compose.AsyncImage
import org.schabi.newpipe.R
import org.schabi.newpipe.extractor.stream.StreamType
import org.schabi.newpipe.ktx.popFirst
import org.schabi.newpipe.ui.components.common.ScaffoldWithToolbar
import org.schabi.newpipe.ui.components.menu.LongPressAction.Type.EnqueueNext
import org.schabi.newpipe.ui.components.menu.LongPressAction.Type.ShowChannelDetails
@ -123,6 +125,9 @@ fun LongPressMenu(
longPressActions: List<LongPressAction>,
onDismissRequest: () -> Unit,
) {
val viewModel: LongPressMenuViewModel = viewModel()
val isHeaderEnabled by viewModel.isHeaderEnabled.collectAsState()
val actionArrangement by viewModel.actionArrangement.collectAsState()
var showEditor by rememberSaveable { mutableStateOf(false) }
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
@ -140,14 +145,39 @@ fun LongPressMenu(
}
}
} else {
val enabledLongPressActions by remember {
derivedStateOf {
actionArrangement.mapNotNull { type ->
longPressActions.firstOrNull { it.type == type }
}
}
}
// show a clickable uploader in the header if an uploader action is available and the
// "show channel details" action is not enabled as a standalone action
val ctx = LocalContext.current
val onUploaderClick by remember {
derivedStateOf {
longPressActions.firstOrNull { it.type == ShowChannelDetails }
?.takeIf { !actionArrangement.contains(ShowChannelDetails) }
?.let { showChannelDetailsAction ->
{
showChannelDetailsAction.action(ctx)
onDismissRequest()
}
}
}
}
ModalBottomSheet(
sheetState = sheetState,
onDismissRequest = onDismissRequest,
dragHandle = { LongPressMenuDragHandle(onEditActions = { showEditor = true }) },
) {
LongPressMenuContent(
longPressable = longPressable,
longPressActions = longPressActions,
header = longPressable.takeIf { isHeaderEnabled },
onUploaderClick = onUploaderClick,
actions = enabledLongPressActions,
onDismissRequest = onDismissRequest,
)
}
@ -156,8 +186,9 @@ fun LongPressMenu(
@Composable
private fun LongPressMenuContent(
longPressable: LongPressable,
longPressActions: List<LongPressAction>,
header: LongPressable?,
onUploaderClick: (() -> Unit)?,
actions: List<LongPressAction>,
onDismissRequest: () -> Unit,
) {
BoxWithConstraints(
@ -172,20 +203,10 @@ private fun LongPressMenuContent(
// width for the landscape/reduced header, measured in button widths
val headerWidthInButtonsReducedSpan = 4
val buttonsPerRow = (this.maxWidth / MinButtonWidth).toInt()
// the channel icon goes in the menu header, so do not show a button for it
val actions = longPressActions.toMutableList()
val ctx = LocalContext.current
val onUploaderClick = actions.popFirst { it.type == ShowChannelDetails }
?.let { showChannelDetailsAction ->
{
showChannelDetailsAction.action(ctx)
onDismissRequest()
}
}
Column {
var actionIndex = -1 // -1 indicates the header
var actionIndex = if (header != null) -1 else 0 // -1 indicates the header
while (actionIndex < actions.size) {
Row(
verticalAlignment = Alignment.CenterVertically,
@ -224,7 +245,7 @@ private fun LongPressMenuContent(
// this branch is taken if the full-span header is going to fit on one
// line (i.e. on phones in portrait)
LongPressMenuHeader(
item = longPressable,
item = header!!, // surely not null since actionIndex < 0
onUploaderClick = onUploaderClick,
modifier = Modifier
.padding(start = 6.dp, end = 6.dp, bottom = 6.dp)
@ -241,7 +262,7 @@ private fun LongPressMenuContent(
// branch is taken, at least two buttons will be on the right side of
// the header (just one button would look off).
LongPressMenuHeader(
item = longPressable,
item = header!!, // surely not null since actionIndex < 0
onUploaderClick = onUploaderClick,
modifier = Modifier
.padding(start = 8.dp, top = 11.dp, bottom = 11.dp)
@ -667,8 +688,9 @@ private fun LongPressMenuPreview(
// longPressable is null when running the preview in an emulator for some reason...
@Suppress("USELESS_ELVIS")
LongPressMenuContent(
longPressable = longPressable ?: LongPressablePreviews().values.first(),
longPressActions = LongPressAction.Type.entries
header = longPressable ?: LongPressablePreviews().values.first(),
onUploaderClick = {},
actions = LongPressAction.Type.entries
// disable Enqueue actions just to show it off
.map { t -> t.buildAction({ t != EnqueueNext }) { } },
onDismissRequest = {},

View File

@ -0,0 +1,50 @@
package org.schabi.newpipe.ui.components.menu
import android.content.Context
import android.content.SharedPreferences
import androidx.lifecycle.ViewModel
import androidx.preference.PreferenceManager
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import org.schabi.newpipe.App
import org.schabi.newpipe.R
/**
* Since view models can't have access to the UI's Context, we use [App.instance] instead to fetch
* shared preferences. This is not the best but won't be needed anyway once we will have a Hilt
* injected repository that provides access to a modern alternative to shared preferences. The whole
* thing with the shared preference listener will not be necessary with the modern alternative.
*/
class LongPressMenuViewModel : ViewModel() {
private val _isHeaderEnabled = MutableStateFlow(
loadIsHeaderEnabledFromSettings(App.instance)
)
val isHeaderEnabled: StateFlow<Boolean> = _isHeaderEnabled.asStateFlow()
private val _actionArrangement = MutableStateFlow(
loadLongPressActionArrangementFromSettings(App.instance)
)
val actionArrangement: StateFlow<List<LongPressAction.Type>> = _actionArrangement.asStateFlow()
private val prefs = PreferenceManager.getDefaultSharedPreferences(App.instance)
private val isHeaderEnabledKey =
App.instance.getString(R.string.long_press_menu_is_header_enabled_key)
private val actionArrangementKey =
App.instance.getString(R.string.long_press_menu_action_arrangement_key)
private val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
if (key == isHeaderEnabledKey) {
_isHeaderEnabled.value = loadIsHeaderEnabledFromSettings(App.instance)
} else if (key == actionArrangementKey) {
_actionArrangement.value = loadLongPressActionArrangementFromSettings(App.instance)
}
}
init {
prefs.registerOnSharedPreferenceChangeListener(listener)
}
override fun onCleared() {
prefs.unregisterOnSharedPreferenceChangeListener(listener)
}
}