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