fix: dialog autofocus and button sizing

This commit is contained in:
edde746
2026-02-08 10:30:14 +01:00
parent 038ec52e00
commit 2a1c99ac06
8 changed files with 115 additions and 118 deletions
+8 -11
View File
@@ -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>();
+13 -1
View File
@@ -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(
+14 -27
View File
@@ -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 -13
View File
@@ -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 {
+8 -15
View File
@@ -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
View File
@@ -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();
}
}
}