fix: dialog autofocus and button sizing
This commit is contained in:
@@ -28,6 +28,7 @@ import '../mixins/item_updatable.dart';
|
||||
import '../mixins/watch_state_aware.dart';
|
||||
import '../utils/watch_state_notifier.dart';
|
||||
import '../utils/app_logger.dart';
|
||||
import '../utils/dialogs.dart';
|
||||
import '../utils/provider_extensions.dart';
|
||||
import '../utils/video_player_navigation.dart';
|
||||
import '../utils/layout_constants.dart';
|
||||
@@ -867,19 +868,15 @@ class _DiscoverScreenState extends State<DiscoverScreen>
|
||||
}
|
||||
|
||||
Future<void> _handleLogout() async {
|
||||
final confirm = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(t.common.logout),
|
||||
content: Text(t.messages.logoutConfirm),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.pop(context, false), child: Text(t.common.cancel)),
|
||||
FilledButton(onPressed: () => Navigator.pop(context, true), child: Text(t.common.logout)),
|
||||
],
|
||||
),
|
||||
final confirm = await showConfirmDialog(
|
||||
context,
|
||||
title: t.common.logout,
|
||||
message: t.messages.logoutConfirm,
|
||||
confirmText: t.common.logout,
|
||||
isDestructive: true,
|
||||
);
|
||||
|
||||
if (confirm == true && mounted) {
|
||||
if (confirm && mounted) {
|
||||
// Use comprehensive logout through UserProfileProvider
|
||||
final userProfileProvider = Provider.of<UserProfileProvider>(context, listen: false);
|
||||
final multiServerProvider = context.read<MultiServerProvider>();
|
||||
|
||||
@@ -198,12 +198,24 @@ class _MainScreenState extends State<MainScreen> with RouteAware, WindowListener
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.pop(dialogContext), child: Text(t.common.later)),
|
||||
TextButton(
|
||||
autofocus: true,
|
||||
onPressed: () => Navigator.pop(dialogContext),
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 14),
|
||||
shape: const StadiumBorder(),
|
||||
),
|
||||
child: Text(t.common.later),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await UpdateService.skipVersion(updateInfo['latestVersion']);
|
||||
if (dialogContext.mounted) Navigator.pop(dialogContext);
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 14),
|
||||
shape: const StadiumBorder(),
|
||||
),
|
||||
child: Text(t.update.skipVersion),
|
||||
),
|
||||
FilledButton(
|
||||
|
||||
@@ -30,6 +30,7 @@ import '../theme/mono_tokens.dart';
|
||||
import '../utils/app_logger.dart';
|
||||
import '../utils/formatters.dart';
|
||||
import '../utils/provider_extensions.dart';
|
||||
import '../utils/dialogs.dart';
|
||||
import '../utils/snackbar_helper.dart';
|
||||
import '../utils/video_player_navigation.dart';
|
||||
import '../widgets/app_bar_back_button.dart';
|
||||
@@ -477,24 +478,20 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> with WatchStateAw
|
||||
return IconButton.filledTonal(
|
||||
onPressed: () async {
|
||||
// Show options: Delete or Retry
|
||||
final action = await showDialog<String>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Cancelled Download'),
|
||||
content: const Text('This download was cancelled. What would you like to do?'),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.pop(context, 'delete'), child: Text(t.common.delete)),
|
||||
TextButton(onPressed: () => Navigator.pop(context, 'retry'), child: const Text('Retry')),
|
||||
],
|
||||
),
|
||||
final retry = await showConfirmDialog(
|
||||
context,
|
||||
title: 'Cancelled Download',
|
||||
message: 'This download was cancelled. What would you like to do?',
|
||||
cancelText: t.common.delete,
|
||||
confirmText: 'Retry',
|
||||
);
|
||||
|
||||
if (action == 'delete' && context.mounted) {
|
||||
if (!retry && context.mounted) {
|
||||
await downloadProvider.deleteDownload(globalKey);
|
||||
if (context.mounted) {
|
||||
showSuccessSnackBar(context, t.downloads.downloadDeleted);
|
||||
}
|
||||
} else if (action == 'retry' && context.mounted) {
|
||||
} else if (retry && context.mounted) {
|
||||
final client = _getClientForMetadata(context);
|
||||
if (client == null) return;
|
||||
await downloadProvider.deleteDownload(globalKey);
|
||||
@@ -559,23 +556,13 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> with WatchStateAw
|
||||
return IconButton.filledTonal(
|
||||
onPressed: () async {
|
||||
// Show delete download confirmation
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(t.downloads.deleteDownload),
|
||||
content: Text(t.downloads.deleteConfirm(title: metadata.title)),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.pop(context, false), child: Text(t.common.cancel)),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
style: TextButton.styleFrom(foregroundColor: Colors.red),
|
||||
child: Text(t.common.delete),
|
||||
),
|
||||
],
|
||||
),
|
||||
final confirmed = await showDeleteConfirmation(
|
||||
context,
|
||||
title: t.downloads.deleteDownload,
|
||||
message: t.downloads.deleteConfirm(title: metadata.title),
|
||||
);
|
||||
|
||||
if (confirmed == true && context.mounted) {
|
||||
if (confirmed && context.mounted) {
|
||||
await downloadProvider.deleteDownload(globalKey);
|
||||
if (context.mounted) {
|
||||
showSuccessSnackBar(context, t.downloads.downloadDeleted);
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:plezy/widgets/app_icon.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import '../../i18n/strings.g.dart';
|
||||
import '../../models/mpv_config_models.dart';
|
||||
import '../../utils/dialogs.dart';
|
||||
import '../../utils/snackbar_helper.dart';
|
||||
import '../../services/settings_service.dart';
|
||||
import '../../widgets/desktop_app_bar.dart';
|
||||
@@ -143,19 +144,8 @@ class _MpvConfigScreenState extends State<MpvConfigScreen> {
|
||||
saveFocusNode.dispose();
|
||||
}
|
||||
|
||||
Future<bool> _showConfirmDeleteDialog({required String title, required String content}) async {
|
||||
final result = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(title),
|
||||
content: Text(content),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.pop(context, false), child: Text(t.common.cancel)),
|
||||
TextButton(onPressed: () => Navigator.pop(context, true), child: Text(t.common.delete)),
|
||||
],
|
||||
),
|
||||
);
|
||||
return result == true;
|
||||
Future<bool> _showConfirmDeleteDialog({required String title, required String content}) {
|
||||
return showDeleteConfirmation(context, title: title, message: content);
|
||||
}
|
||||
|
||||
Future<void> _showDeleteEntryDialog(int index) async {
|
||||
|
||||
@@ -37,6 +37,7 @@ import '../services/shader_service.dart';
|
||||
import '../providers/shader_provider.dart';
|
||||
import '../providers/user_profile_provider.dart';
|
||||
import '../utils/app_logger.dart';
|
||||
import '../utils/dialogs.dart';
|
||||
import '../utils/orientation_helper.dart';
|
||||
import '../utils/platform_detector.dart';
|
||||
import '../utils/provider_extensions.dart';
|
||||
@@ -1131,23 +1132,15 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
|
||||
try {
|
||||
// For non-host participants, show leave session confirmation
|
||||
if (_watchTogetherProvider != null && _watchTogetherProvider!.isInSession && !_watchTogetherProvider!.isHost) {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (dialogContext) => AlertDialog(
|
||||
title: const Text('Leave Session?'),
|
||||
content: const Text('You will be removed from the session.'),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.pop(dialogContext, false), child: const Text('Cancel')),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.pop(dialogContext, true),
|
||||
style: FilledButton.styleFrom(backgroundColor: Theme.of(dialogContext).colorScheme.error),
|
||||
child: const Text('Leave'),
|
||||
),
|
||||
],
|
||||
),
|
||||
final confirmed = await showConfirmDialog(
|
||||
context,
|
||||
title: 'Leave Session?',
|
||||
message: 'You will be removed from the session.',
|
||||
confirmText: 'Leave',
|
||||
isDestructive: true,
|
||||
);
|
||||
|
||||
if (confirmed == true && mounted) {
|
||||
if (confirmed && mounted) {
|
||||
await _watchTogetherProvider!.leaveSession();
|
||||
if (mounted) {
|
||||
// Exit fullscreen before leaving player (Windows/Linux only)
|
||||
|
||||
+39
-15
@@ -3,28 +3,52 @@ import '../i18n/strings.g.dart';
|
||||
|
||||
/// Utility functions for showing common dialogs
|
||||
|
||||
/// Shows a delete confirmation dialog
|
||||
/// Returns true if user confirmed, false if cancelled
|
||||
Future<bool> showDeleteConfirmation(BuildContext context, {required String title, required String message}) async {
|
||||
const _buttonPadding = EdgeInsets.symmetric(horizontal: 18, vertical: 14);
|
||||
const _buttonShape = StadiumBorder();
|
||||
|
||||
/// Shows a confirmation dialog with consistent button sizing and autofocus.
|
||||
/// Returns true if user confirmed, false if cancelled.
|
||||
Future<bool> showConfirmDialog(
|
||||
BuildContext context, {
|
||||
required String title,
|
||||
required String message,
|
||||
required String confirmText,
|
||||
String? cancelText,
|
||||
bool isDestructive = false,
|
||||
}) async {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(title),
|
||||
content: Text(message),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.pop(context, false), child: Text(t.common.cancel)),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
style: TextButton.styleFrom(foregroundColor: Colors.red),
|
||||
child: Text(t.common.delete),
|
||||
),
|
||||
],
|
||||
),
|
||||
builder: (dialogContext) {
|
||||
final colorScheme = Theme.of(dialogContext).colorScheme;
|
||||
return AlertDialog(
|
||||
title: Text(title),
|
||||
content: Text(message),
|
||||
actions: [
|
||||
TextButton(
|
||||
autofocus: true,
|
||||
onPressed: () => Navigator.pop(dialogContext, false),
|
||||
style: TextButton.styleFrom(padding: _buttonPadding, shape: _buttonShape),
|
||||
child: Text(cancelText ?? t.common.cancel),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.pop(dialogContext, true),
|
||||
style: isDestructive ? FilledButton.styleFrom(backgroundColor: colorScheme.error) : null,
|
||||
child: Text(confirmText),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
return confirmed ?? false;
|
||||
}
|
||||
|
||||
/// Shows a delete confirmation dialog.
|
||||
/// Convenience wrapper around [showConfirmDialog] with destructive styling.
|
||||
Future<bool> showDeleteConfirmation(BuildContext context, {required String title, required String message}) {
|
||||
return showConfirmDialog(context, title: title, message: message, confirmText: t.common.delete, isDestructive: true);
|
||||
}
|
||||
|
||||
/// Shows a text input dialog for creating/naming items
|
||||
/// Returns the entered text, or null if cancelled
|
||||
Future<String?> showTextInputDialog(
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:provider/provider.dart';
|
||||
|
||||
import '../../i18n/strings.g.dart';
|
||||
import '../../utils/app_logger.dart';
|
||||
import '../../utils/dialogs.dart';
|
||||
import '../../widgets/focused_scroll_scaffold.dart';
|
||||
import '../models/watch_session.dart';
|
||||
import '../providers/watch_together_provider.dart';
|
||||
@@ -143,15 +144,23 @@ class _NotInSessionViewState extends State<_NotInSessionView> {
|
||||
}
|
||||
|
||||
Future<ControlMode?> _showControlModeDialog() {
|
||||
const buttonPadding = EdgeInsets.symmetric(horizontal: 18, vertical: 14);
|
||||
const buttonShape = StadiumBorder();
|
||||
return showDialog<ControlMode>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(t.watchTogether.controlMode),
|
||||
content: Text(t.watchTogether.controlModeQuestion),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.pop(context), child: Text(t.common.cancel)),
|
||||
TextButton(
|
||||
autofocus: true,
|
||||
onPressed: () => Navigator.pop(context),
|
||||
style: TextButton.styleFrom(padding: buttonPadding, shape: buttonShape),
|
||||
child: Text(t.common.cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, ControlMode.hostOnly),
|
||||
style: TextButton.styleFrom(padding: buttonPadding, shape: buttonShape),
|
||||
child: Text(t.watchTogether.hostOnly),
|
||||
),
|
||||
FilledButton(
|
||||
@@ -336,23 +345,15 @@ class _ActiveSessionContent extends StatelessWidget {
|
||||
}
|
||||
|
||||
Future<void> _leaveSession(BuildContext context) async {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(watchTogether.isHost ? t.watchTogether.endSessionQuestion : t.watchTogether.leaveSessionQuestion),
|
||||
content: Text(watchTogether.isHost ? t.watchTogether.endSessionConfirm : t.watchTogether.leaveSessionConfirm),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.pop(context, false), child: Text(t.common.cancel)),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
style: FilledButton.styleFrom(backgroundColor: Theme.of(context).colorScheme.error),
|
||||
child: Text(watchTogether.isHost ? t.watchTogether.end : t.watchTogether.leave),
|
||||
),
|
||||
],
|
||||
),
|
||||
final confirmed = await showConfirmDialog(
|
||||
context,
|
||||
title: watchTogether.isHost ? t.watchTogether.endSessionQuestion : t.watchTogether.leaveSessionQuestion,
|
||||
message: watchTogether.isHost ? t.watchTogether.endSessionConfirm : t.watchTogether.leaveSessionConfirm,
|
||||
confirmText: watchTogether.isHost ? t.watchTogether.end : t.watchTogether.leave,
|
||||
isDestructive: true,
|
||||
);
|
||||
|
||||
if (confirmed == true) {
|
||||
if (confirmed) {
|
||||
await watchTogether.leaveSession();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../i18n/strings.g.dart';
|
||||
import '../../utils/dialogs.dart';
|
||||
import '../../utils/snackbar_helper.dart';
|
||||
import '../models/watch_session.dart';
|
||||
import '../providers/watch_together_provider.dart';
|
||||
@@ -259,27 +260,19 @@ class _SessionMenuSheet extends StatelessWidget {
|
||||
showSuccessSnackBar(context, t.watchTogether.sessionCodeCopied);
|
||||
}
|
||||
|
||||
void _confirmLeave(BuildContext context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(provider.isHost ? t.watchTogether.endSessionQuestion : t.watchTogether.leaveSessionQuestion),
|
||||
content: Text(
|
||||
provider.isHost ? t.watchTogether.endSessionConfirmOverlay : t.watchTogether.leaveSessionConfirmOverlay,
|
||||
),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.pop(context), child: Text(t.common.cancel)),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
provider.leaveSession();
|
||||
onLeaveSession?.call();
|
||||
},
|
||||
child: Text(provider.isHost ? t.watchTogether.endSession : t.watchTogether.leave),
|
||||
),
|
||||
],
|
||||
),
|
||||
void _confirmLeave(BuildContext context) async {
|
||||
final confirmed = await showConfirmDialog(
|
||||
context,
|
||||
title: provider.isHost ? t.watchTogether.endSessionQuestion : t.watchTogether.leaveSessionQuestion,
|
||||
message: provider.isHost ? t.watchTogether.endSessionConfirmOverlay : t.watchTogether.leaveSessionConfirmOverlay,
|
||||
confirmText: provider.isHost ? t.watchTogether.endSession : t.watchTogether.leave,
|
||||
isDestructive: true,
|
||||
);
|
||||
|
||||
if (confirmed) {
|
||||
provider.leaveSession();
|
||||
onLeaveSession?.call();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user