feature: More info playback state (#219)

Co-authored-by: PartyDonut <PartyDonut@users.noreply.github.com>
This commit is contained in:
PartyDonut 2025-02-08 11:48:42 +01:00 committed by GitHub
parent cf53f02d90
commit f259151336
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 112 additions and 186 deletions

View file

@ -1167,5 +1167,6 @@
"tablet": "Tablet", "tablet": "Tablet",
"desktop": "Desktop", "desktop": "Desktop",
"layoutModeSingle": "Single", "layoutModeSingle": "Single",
"layoutModeDual": "Dual" "layoutModeDual": "Dual",
"copiedToClipboard": "Copied to clipboard"
} }

View file

@ -1,12 +1,11 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/models/error_log_model.dart'; import 'package:fladder/models/error_log_model.dart';
import 'package:fladder/providers/crash_log_provider.dart'; import 'package:fladder/providers/crash_log_provider.dart';
import 'package:fladder/screens/shared/fladder_snackbar.dart'; import 'package:fladder/util/clipboard_helper.dart';
import 'package:fladder/util/list_padding.dart'; import 'package:fladder/util/list_padding.dart';
import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/localization_helper.dart';
import 'package:fladder/util/string_extensions.dart'; import 'package:fladder/util/string_extensions.dart';
@ -102,12 +101,7 @@ class CrashScreen extends ConsumerWidget {
), ),
), ),
IconButton( IconButton(
onPressed: () async { onPressed: () => context.copyToClipboard(e.clipBoard),
await Clipboard.setData(ClipboardData(text: e.clipBoard));
if (context.mounted) {
fladderSnackbar(context, title: "Copied to clipboard");
}
},
icon: const Icon(Icons.copy_all_rounded), icon: const Icon(Icons.copy_all_rounded),
), ),
], ],

View file

@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:ficonsax/ficonsax.dart'; import 'package:ficonsax/ficonsax.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
@ -7,7 +6,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/models/information_model.dart'; import 'package:fladder/models/information_model.dart';
import 'package:fladder/models/item_base_model.dart'; import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/providers/items/information_provider.dart'; import 'package:fladder/providers/items/information_provider.dart';
import 'package:fladder/screens/shared/fladder_snackbar.dart'; import 'package:fladder/util/clipboard_helper.dart';
import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/localization_helper.dart';
import 'package:fladder/widgets/shared/clickable_text.dart'; import 'package:fladder/widgets/shared/clickable_text.dart';
@ -79,12 +78,7 @@ class ItemInfoScreenState extends ConsumerState<ItemInfoScreen> {
const Spacer(), const Spacer(),
const SizedBox(width: 6), const SizedBox(width: 6),
IconButton( IconButton(
onPressed: () async { onPressed: () => context.copyToClipboard(info.model.toString()),
await Clipboard.setData(ClipboardData(text: info.model.toString()));
if (context.mounted) {
fladderSnackbar(context, title: "Copied to clipboard");
}
},
icon: const Icon(Icons.copy_all_rounded)), icon: const Icon(Icons.copy_all_rounded)),
const SizedBox(width: 6), const SizedBox(width: 6),
IconButton( IconButton(
@ -171,10 +165,7 @@ class ItemInfoScreenState extends ConsumerState<ItemInfoScreen> {
Flexible( Flexible(
child: ClickableText( child: ClickableText(
text: title, text: title,
onTap: () async { onTap: () => context.copyToClipboard(value),
await Clipboard.setData(ClipboardData(text: value));
fladderSnackbar(context, title: "Copied to clipboard");
},
style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold), style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
), ),
), ),
@ -211,10 +202,7 @@ class ItemInfoScreenState extends ConsumerState<ItemInfoScreen> {
), ),
), ),
IconButton( IconButton(
onPressed: () async { onPressed: () => context.copyToClipboard(InformationModel.mapToString(map)),
await Clipboard.setData(ClipboardData(text: InformationModel.mapToString(map)));
fladderSnackbar(context, title: "Copied to clipboard");
},
icon: const Icon(Icons.copy_all_rounded)) icon: const Icon(Icons.copy_all_rounded))
], ],
), ),

View file

@ -1,6 +1,7 @@
import 'package:chopper/chopper.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:chopper/chopper.dart';
void fladderSnackbar( void fladderSnackbar(
BuildContext context, { BuildContext context, {
String title = "", String title = "",
@ -34,156 +35,3 @@ void fladderSnackbarResponse(BuildContext context, Response? response, {String?
fladderSnackbar(context, title: altTitle); fladderSnackbar(context, title: altTitle);
} }
} }
// void _showOverlay(
// BuildContext context, {
// required String title,
// Widget? leading,
// bool showCloseButton = false,
// bool permanent = false,
// Duration duration = const Duration(seconds: 3),
// }) {
// late OverlayEntry overlayEntry;
// overlayEntry = OverlayEntry(
// builder: (context) => _OverlayAnimationWidget(
// title: title,
// leading: leading,
// showCloseButton: showCloseButton,
// permanent: permanent,
// duration: duration,
// overlayEntry: overlayEntry,
// ),
// );
// // Insert the overlay entry into the overlay
// Overlay.of(context).insert(overlayEntry);
// }
// class _OverlayAnimationWidget extends StatefulWidget {
// final String title;
// final Widget? leading;
// final bool showCloseButton;
// final bool permanent;
// final Duration duration;
// final OverlayEntry overlayEntry;
// _OverlayAnimationWidget({
// required this.title,
// this.leading,
// this.showCloseButton = false,
// this.permanent = false,
// this.duration = const Duration(seconds: 3),
// required this.overlayEntry,
// });
// @override
// _OverlayAnimationWidgetState createState() => _OverlayAnimationWidgetState();
// }
// class _OverlayAnimationWidgetState extends State<_OverlayAnimationWidget> with SingleTickerProviderStateMixin {
// late AnimationController _controller;
// late Animation<Offset> _offsetAnimation;
// void remove() {
// // Optionally, you can use a Future.delayed to remove the overlay after a certain duration
// _controller.reverse();
// // Remove the overlay entry after the animation completes
// Future.delayed(Duration(seconds: 1), () {
// widget.overlayEntry.remove();
// });
// }
// @override
// void initState() {
// super.initState();
// _controller = AnimationController(
// vsync: this,
// duration: Duration(milliseconds: 250),
// );
// _offsetAnimation = Tween<Offset>(
// begin: Offset(0.0, 1.5),
// end: Offset.zero,
// ).animate(CurvedAnimation(
// parent: _controller,
// curve: Curves.fastOutSlowIn,
// ));
// // Start the animation
// _controller.forward();
// Future.delayed(widget.duration, () {
// if (!widget.permanent) {
// remove();
// }
// });
// }
// @override
// void dispose() {
// _controller.dispose();
// super.dispose();
// }
// @override
// Widget build(BuildContext context) {
// return Positioned(
// bottom: 10 + MediaQuery.of(context).padding.bottom,
// left: 25,
// right: 25,
// child: Dismissible(
// key: UniqueKey(),
// direction: DismissDirection.horizontal,
// confirmDismiss: (direction) async {
// remove();
// return true;
// },
// child: SlideTransition(
// position: _offsetAnimation,
// child: Card(
// elevation: 5,
// color: Colors.transparent,
// surfaceTintColor: Colors.transparent,
// child: Container(
// decoration: BoxDecoration(
// color: Theme.of(context).colorScheme.secondaryContainer,
// ),
// child: Padding(
// padding: const EdgeInsets.all(12.0),
// child: ConstrainedBox(
// constraints: BoxConstraints(minHeight: 45),
// child: Row(
// children: [
// if (widget.leading != null) widget.leading!,
// Expanded(
// child: Text(
// widget.title,
// style: TextStyle(
// fontSize: 16,
// fontWeight: FontWeight.w400,
// color: Theme.of(context).colorScheme.onSecondaryContainer),
// ),
// ),
// const SizedBox(width: 6),
// if (widget.showCloseButton || widget.permanent)
// IconButton(
// onPressed: () => remove(),
// icon: Icon(
// IconsaxOutline.close_square,
// size: 28,
// color: Theme.of(context).colorScheme.onSecondaryContainer,
// ),
// )
// ],
// ),
// ),
// ),
// ),
// ),
// ),
// ),
// );
// }
// }

View file

@ -1,12 +1,18 @@
import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:ficonsax/ficonsax.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/models/playback/playback_model.dart'; import 'package:fladder/models/playback/playback_model.dart';
import 'package:fladder/providers/session_info_provider.dart'; import 'package:fladder/providers/session_info_provider.dart';
import 'package:fladder/providers/video_player_provider.dart'; import 'package:fladder/providers/video_player_provider.dart';
import 'package:fladder/util/clipboard_helper.dart';
import 'package:fladder/util/humanize_duration.dart';
import 'package:fladder/util/list_padding.dart'; import 'package:fladder/util/list_padding.dart';
import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/localization_helper.dart';
import 'package:fladder/wrappers/players/player_states.dart';
Future<void> showVideoPlaybackInformation(BuildContext context) { Future<void> showVideoPlaybackInformation(BuildContext context) {
return showDialog( return showDialog(
@ -23,6 +29,7 @@ class _VideoPlaybackInformation extends ConsumerWidget {
final playbackModel = ref.watch(playBackModel); final playbackModel = ref.watch(playBackModel);
final sessionInfo = ref.watch(sessionInfoProvider); final sessionInfo = ref.watch(sessionInfoProvider);
final backend = ref.read(videoPlayerProvider.select((value) => value.backend)); final backend = ref.read(videoPlayerProvider.select((value) => value.backend));
final playbackState = ref.watch(videoPlayerProvider.select((value) => value.lastState));
return Dialog( return Dialog(
child: Padding( child: Padding(
padding: const EdgeInsets.all(12.0), padding: const EdgeInsets.all(12.0),
@ -42,12 +49,37 @@ class _VideoPlaybackInformation extends ConsumerWidget {
Row( Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [const Text('backend: '), Text(backend?.label(context) ?? context.localized.unknown)], children: [const Text('backend: '), Text(backend?.label(context) ?? context.localized.unknown)],
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
const Text('url: '),
const SizedBox(width: 8),
Flexible(
child: ImageFiltered(
imageFilter: ImageFilter.blur(
sigmaX: 3.0,
sigmaY: 3.0,
),
child: Text(
playbackModel?.media?.url ?? "No url",
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
),
IconButton.filled(
onPressed: () => context.copyToClipboard(playbackModel?.media?.url ?? "No url"),
icon: const Icon(IconsaxOutline.copy),
)
],
) )
].addPadding(const EdgeInsets.symmetric(vertical: 3)), ].addPadding(const EdgeInsets.symmetric(vertical: 3)),
), ),
), ),
), ),
const Divider(), const Divider(),
if (playbackState != null) _PlayerInformation(state: playbackState),
Text("Playback information", style: Theme.of(context).textTheme.titleMedium), Text("Playback information", style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 4), const SizedBox(height: 4),
Padding( Padding(
@ -113,3 +145,54 @@ class _VideoPlaybackInformation extends ConsumerWidget {
); );
} }
} }
class _PlayerInformation extends StatelessWidget {
final PlayerState state;
const _PlayerInformation({
required this.state,
});
@override
Widget build(
BuildContext context,
) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Player state", style: Theme.of(context).textTheme.titleMedium),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4).copyWith(top: 4),
child: Opacity(
opacity: 0.80,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: [const Text('playing: '), Text(state.playing.toString())],
),
Row(
mainAxisSize: MainAxisSize.min,
children: [const Text('buffering: '), Text(state.buffering.toString())],
),
Row(
mainAxisSize: MainAxisSize.min,
children: [const Text('duration: '), Text(state.duration.humanize ?? "")],
),
Row(
mainAxisSize: MainAxisSize.min,
children: [const Text('rate: '), Text(state.rate.toString())],
),
Row(
mainAxisSize: MainAxisSize.min,
children: [const Text('volume: '), Text(state.volume.toString())],
),
].addPadding(const EdgeInsets.symmetric(vertical: 3)),
),
),
),
const Divider(),
],
);
}
}

View file

@ -0,0 +1,17 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:fladder/screens/shared/fladder_snackbar.dart';
import 'package:fladder/util/localization_helper.dart';
extension ClipboardHelper on BuildContext {
Future<void> copyToClipboard(String value, {String? customMessage}) async {
await Clipboard.setData(ClipboardData(text: value));
if (mounted) {
fladderSnackbar(
this,
title: customMessage ?? localized.copiedToClipboard,
);
}
}
}

View file

@ -1,6 +1,5 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:ficonsax/ficonsax.dart'; import 'package:ficonsax/ficonsax.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
@ -21,6 +20,7 @@ import 'package:fladder/screens/playlists/add_to_playlists.dart';
import 'package:fladder/screens/shared/fladder_snackbar.dart'; import 'package:fladder/screens/shared/fladder_snackbar.dart';
import 'package:fladder/screens/syncing/sync_button.dart'; import 'package:fladder/screens/syncing/sync_button.dart';
import 'package:fladder/screens/syncing/sync_item_details.dart'; import 'package:fladder/screens/syncing/sync_item_details.dart';
import 'package:fladder/util/clipboard_helper.dart';
import 'package:fladder/util/file_downloader.dart'; import 'package:fladder/util/file_downloader.dart';
import 'package:fladder/util/item_base_model/play_item_helpers.dart'; import 'package:fladder/util/item_base_model/play_item_helpers.dart';
import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/localization_helper.dart';
@ -223,12 +223,7 @@ extension ItemBaseModelExtensions on ItemBaseModel {
), ),
ItemActionButton( ItemActionButton(
icon: const Icon(IconsaxOutline.link_21), icon: const Icon(IconsaxOutline.link_21),
action: () async { action: () => context.copyToClipboard(downloadUrl),
await Clipboard.setData(ClipboardData(text: downloadUrl));
if (context.mounted) {
fladderSnackbar(context, title: "Copied URL to clipboard");
}
},
label: Text(context.localized.copyStreamUrl), label: Text(context.localized.copyStreamUrl),
) )
], ],