@@ -7,6 +7,7 @@ import android.content.Context
77import android.content.res.Configuration
88import android.view.ViewGroup
99import android.view.ViewGroup.LayoutParams
10+ import androidx.activity.compose.BackHandler
1011import androidx.compose.foundation.clickable
1112import androidx.compose.foundation.isSystemInDarkTheme
1213import androidx.compose.foundation.layout.Arrangement
@@ -37,16 +38,15 @@ import androidx.compose.material3.IconButton
3738import androidx.compose.material3.MaterialTheme
3839import androidx.compose.material3.ModalBottomSheet
3940import androidx.compose.material3.OutlinedButton
40- import androidx.compose.material3.SheetState
4141import androidx.compose.material3.Surface
4242import androidx.compose.material3.Text
4343import androidx.compose.material3.rememberModalBottomSheetState
44- import androidx.compose.material3.rememberStandardBottomSheetState
4544import androidx.compose.runtime.Composable
4645import androidx.compose.runtime.DisposableEffect
4746import androidx.compose.runtime.getValue
4847import androidx.compose.runtime.mutableStateOf
4948import androidx.compose.runtime.remember
49+ import androidx.compose.runtime.saveable.rememberSaveable
5050import androidx.compose.runtime.setValue
5151import androidx.compose.ui.Alignment
5252import androidx.compose.ui.Modifier
@@ -73,6 +73,7 @@ import coil3.compose.AsyncImage
7373import org.schabi.newpipe.R
7474import org.schabi.newpipe.extractor.stream.StreamType
7575import org.schabi.newpipe.ktx.popFirst
76+ import org.schabi.newpipe.ui.components.common.ScaffoldWithToolbar
7677import org.schabi.newpipe.ui.components.menu.LongPressAction.Type.EnqueueNext
7778import org.schabi.newpipe.ui.components.menu.LongPressAction.Type.ShowChannelDetails
7879import org.schabi.newpipe.ui.theme.AppTheme
@@ -119,100 +120,127 @@ fun LongPressMenu(
119120 longPressable : LongPressable ,
120121 longPressActions : List <LongPressAction >,
121122 onDismissRequest : () -> Unit ,
122- onEditActions : () -> Unit = {}, // TODO handle this menu
123- sheetState : SheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true),
124123) {
125- ModalBottomSheet (
126- onDismissRequest,
127- sheetState = sheetState,
128- dragHandle = { LongPressMenuDragHandle (onEditActions) },
129- ) {
130- BoxWithConstraints (
131- modifier = Modifier
132- .fillMaxWidth()
133- .padding(start = 6 .dp, end = 6 .dp, bottom = 16 .dp)
124+ var showEditor by rememberSaveable(key = longPressable.url) { mutableStateOf(false ) }
125+ val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true )
126+
127+ if (showEditor) {
128+ // we can't put the editor in a bottom sheet, because it relies on dragging gestures
129+ ScaffoldWithToolbar (
130+ title = stringResource(R .string.long_press_menu_actions_editor),
131+ onBackClick = { showEditor = false },
132+ ) { paddingValues ->
133+ Box (modifier = Modifier .padding(paddingValues)) {
134+ LongPressMenuEditor ()
135+ }
136+ BackHandler { showEditor = false }
137+ }
138+ } else {
139+ ModalBottomSheet (
140+ sheetState = sheetState,
141+ onDismissRequest = onDismissRequest,
142+ dragHandle = { LongPressMenuDragHandle (onEditActions = { showEditor = true }) },
134143 ) {
135- val buttonHeight = MinButtonWidth // landscape aspect ratio, square in the limit
136- val headerWidthInButtons = 5 // the header is 5 times as wide as the buttons
137- val buttonsPerRow = (this .maxWidth / MinButtonWidth ).toInt()
144+ LongPressMenuContent (
145+ longPressable = longPressable,
146+ longPressActions = longPressActions,
147+ onDismissRequest = onDismissRequest,
148+ )
149+ }
150+ }
151+ }
138152
139- // the channel icon goes in the menu header, so do not show a button for it
140- val actions = longPressActions.toMutableList()
141- val ctx = LocalContext .current
142- val onUploaderClick = actions.popFirst { it.type == ShowChannelDetails }
143- ?.let { showChannelDetailsAction ->
144- {
145- showChannelDetailsAction.action(ctx)
146- onDismissRequest()
147- }
153+ @Composable
154+ private fun LongPressMenuContent (
155+ longPressable : LongPressable ,
156+ longPressActions : List <LongPressAction >,
157+ onDismissRequest : () -> Unit ,
158+ ) {
159+ BoxWithConstraints (
160+ modifier = Modifier
161+ .fillMaxWidth()
162+ .padding(start = 6 .dp, end = 6 .dp, bottom = 16 .dp)
163+ ) {
164+ val buttonHeight = MinButtonWidth // landscape aspect ratio, square in the limit
165+ val headerWidthInButtons = 5 // the header is 5 times as wide as the buttons
166+ val buttonsPerRow = (this .maxWidth / MinButtonWidth ).toInt()
167+
168+ // the channel icon goes in the menu header, so do not show a button for it
169+ val actions = longPressActions.toMutableList()
170+ val ctx = LocalContext .current
171+ val onUploaderClick = actions.popFirst { it.type == ShowChannelDetails }
172+ ?.let { showChannelDetailsAction ->
173+ {
174+ showChannelDetailsAction.action(ctx)
175+ onDismissRequest()
148176 }
177+ }
149178
150- Column {
151- var actionIndex = - 1 // -1 indicates the header
152- while (actionIndex < actions.size) {
153- Row (
154- verticalAlignment = Alignment .CenterVertically ,
155- modifier = Modifier .fillMaxWidth(),
156- ) {
157- var rowIndex = 0
158- while (rowIndex < buttonsPerRow) {
159- if (actionIndex >= actions.size) {
160- // no more buttons to show, fill the rest of the row with a
161- // spacer that has the same weight as the missing buttons, so that
162- // the other buttons don't grow too wide
163- Spacer (
164- modifier = Modifier
165- .height(buttonHeight)
166- .fillMaxWidth()
167- .weight((buttonsPerRow - rowIndex).toFloat()),
168- )
169- break
170- } else if (actionIndex >= 0 ) {
171- val action = actions[actionIndex]
172- LongPressMenuButton (
173- icon = action.type.icon,
174- text = stringResource(action.type.label),
175- onClick = {
176- action.action(ctx)
177- onDismissRequest()
178- },
179- enabled = action.enabled(false ),
180- modifier = Modifier
181- .height(buttonHeight)
182- .fillMaxWidth()
183- .weight(1F ),
184- )
185- rowIndex + = 1
186- } else if (headerWidthInButtons >= buttonsPerRow) {
187- // this branch is taken if the header is going to fit on one line
188- // (i.e. on phones in portrait)
189- LongPressMenuHeader (
190- item = longPressable,
191- onUploaderClick = onUploaderClick,
192- modifier = Modifier
193- // leave the height as small as possible, since it's the
194- // only item on the row anyway
195- .padding(start = 6 .dp, end = 6 .dp, bottom = 6 .dp)
196- .fillMaxWidth()
197- .weight(headerWidthInButtons.toFloat()),
198- )
199- rowIndex + = headerWidthInButtons
200- } else {
201- // this branch is taken if the header will have some buttons to its
202- // right (i.e. on tablets or on phones in landscape)
203- LongPressMenuHeader (
204- item = longPressable,
205- onUploaderClick = onUploaderClick,
206- modifier = Modifier
207- .padding(6 .dp)
208- .heightIn(min = 70 .dp)
209- .fillMaxWidth()
210- .weight(headerWidthInButtons.toFloat()),
211- )
212- rowIndex + = headerWidthInButtons
213- }
214- actionIndex + = 1
179+ Column {
180+ var actionIndex = - 1 // -1 indicates the header
181+ while (actionIndex < actions.size) {
182+ Row (
183+ verticalAlignment = Alignment .CenterVertically ,
184+ modifier = Modifier .fillMaxWidth(),
185+ ) {
186+ var rowIndex = 0
187+ while (rowIndex < buttonsPerRow) {
188+ if (actionIndex >= actions.size) {
189+ // no more buttons to show, fill the rest of the row with a
190+ // spacer that has the same weight as the missing buttons, so that
191+ // the other buttons don't grow too wide
192+ Spacer (
193+ modifier = Modifier
194+ .height(buttonHeight)
195+ .fillMaxWidth()
196+ .weight((buttonsPerRow - rowIndex).toFloat()),
197+ )
198+ break
199+ } else if (actionIndex >= 0 ) {
200+ val action = actions[actionIndex]
201+ LongPressMenuButton (
202+ icon = action.type.icon,
203+ text = stringResource(action.type.label),
204+ onClick = {
205+ action.action(ctx)
206+ onDismissRequest()
207+ },
208+ enabled = action.enabled(false ),
209+ modifier = Modifier
210+ .height(buttonHeight)
211+ .fillMaxWidth()
212+ .weight(1F ),
213+ )
214+ rowIndex + = 1
215+ } else if (headerWidthInButtons >= buttonsPerRow) {
216+ // this branch is taken if the header is going to fit on one line
217+ // (i.e. on phones in portrait)
218+ LongPressMenuHeader (
219+ item = longPressable,
220+ onUploaderClick = onUploaderClick,
221+ modifier = Modifier
222+ // leave the height as small as possible, since it's the
223+ // only item on the row anyway
224+ .padding(start = 6 .dp, end = 6 .dp, bottom = 6 .dp)
225+ .fillMaxWidth()
226+ .weight(headerWidthInButtons.toFloat()),
227+ )
228+ rowIndex + = headerWidthInButtons
229+ } else {
230+ // this branch is taken if the header will have some buttons to its
231+ // right (i.e. on tablets or on phones in landscape)
232+ LongPressMenuHeader (
233+ item = longPressable,
234+ onUploaderClick = onUploaderClick,
235+ modifier = Modifier
236+ .padding(6 .dp)
237+ .heightIn(min = 70 .dp)
238+ .fillMaxWidth()
239+ .weight(headerWidthInButtons.toFloat()),
240+ )
241+ rowIndex + = headerWidthInButtons
215242 }
243+ actionIndex + = 1
216244 }
217245 }
218246 }
@@ -619,14 +647,12 @@ private fun LongPressMenuPreview(
619647 AppTheme (useDarkTheme = useDarkTheme) {
620648 // longPressable is null when running the preview in an emulator for some reason...
621649 @Suppress(" USELESS_ELVIS" )
622- LongPressMenu (
650+ LongPressMenuContent (
623651 longPressable = longPressable ? : LongPressablePreviews ().values.first(),
624- onDismissRequest = {},
625652 longPressActions = LongPressAction .Type .entries
626653 // disable Enqueue actions just to show it off
627654 .map { t -> t.buildAction({ t != EnqueueNext }) { } },
628- onEditActions = { useDarkTheme = ! useDarkTheme },
629- sheetState = rememberStandardBottomSheetState(), // makes it start out as open
655+ onDismissRequest = {},
630656 )
631657 }
632658}
0 commit comments