dialog_utils.dart 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. import 'package:dio/dio.dart';
  2. import 'package:flutter/material.dart';
  3. import '../l10n/app_localizations.dart';
  4. import '../network/api_response.dart';
  5. /// 从异常中提取用户可读的错误信息(只取业务 message,去掉 Dio/错误码前缀)
  6. String extractErrorMessage(Object e) {
  7. if (e is DioException) {
  8. final data = e.response?.data;
  9. if (data is Map) {
  10. final msg = data['message']?.toString().trim() ??
  11. data['msg']?.toString().trim() ??
  12. '';
  13. if (msg.isNotEmpty) {
  14. return msg;
  15. }
  16. }
  17. if (e.error is ApiException) {
  18. return (e.error as ApiException).message;
  19. }
  20. }
  21. if (e is ApiException) {
  22. return e.message;
  23. }
  24. return e.toString();
  25. }
  26. /// 通用确认弹框,返回 true 表示用户点击了确定
  27. Future<bool> showConfirmDialog(
  28. BuildContext context, {
  29. required String content,
  30. String? cancelText,
  31. String? confirmText,
  32. }) async {
  33. final l10n = AppLocalizations.of(context)!;
  34. final cancelLabel = cancelText ?? l10n.cancel;
  35. final confirmLabel = confirmText ?? l10n.confirm;
  36. final result = await showDialog<bool>(
  37. context: context,
  38. barrierDismissible: false,
  39. builder: (ctx) {
  40. final cs = Theme.of(ctx).colorScheme;
  41. return AlertDialog(
  42. shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
  43. contentPadding: const EdgeInsets.fromLTRB(24, 28, 24, 8),
  44. actionsPadding: EdgeInsets.zero,
  45. content: Text(
  46. content,
  47. textAlign: TextAlign.center,
  48. style: const TextStyle(fontSize: 15, height: 1.5),
  49. ),
  50. actions: [
  51. const Divider(height: 1),
  52. Row(
  53. children: [
  54. Expanded(
  55. child: TextButton(
  56. onPressed: () => Navigator.of(ctx).pop(false),
  57. style: TextButton.styleFrom(
  58. foregroundColor: Colors.grey,
  59. padding: const EdgeInsets.symmetric(vertical: 14),
  60. shape: const RoundedRectangleBorder(
  61. borderRadius: BorderRadius.only(
  62. bottomLeft: Radius.circular(16),
  63. ),
  64. ),
  65. ),
  66. child: Text(cancelLabel, style: const TextStyle(fontSize: 15)),
  67. ),
  68. ),
  69. const VerticalDivider(width: 1),
  70. Expanded(
  71. child: TextButton(
  72. onPressed: () => Navigator.of(ctx).pop(true),
  73. style: TextButton.styleFrom(
  74. foregroundColor: cs.onSurface,
  75. padding: const EdgeInsets.symmetric(vertical: 14),
  76. shape: const RoundedRectangleBorder(
  77. borderRadius: BorderRadius.only(
  78. bottomRight: Radius.circular(16),
  79. ),
  80. ),
  81. ),
  82. child: Text(
  83. confirmLabel,
  84. style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600),
  85. ),
  86. ),
  87. ),
  88. ],
  89. ),
  90. ],
  91. );
  92. },
  93. );
  94. return result ?? false;
  95. }
  96. /// 通用提示弹框(仅一个确定按钮)
  97. Future<void> showTipDialog(
  98. BuildContext context, {
  99. required String content,
  100. String? confirmText,
  101. }) async {
  102. final confirmLabel = confirmText ?? AppLocalizations.of(context)!.confirm;
  103. await showDialog<void>(
  104. context: context,
  105. builder: (ctx) {
  106. final cs = Theme.of(ctx).colorScheme;
  107. return AlertDialog(
  108. shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
  109. contentPadding: const EdgeInsets.fromLTRB(24, 28, 24, 8),
  110. actionsPadding: EdgeInsets.zero,
  111. content: Text(
  112. content,
  113. textAlign: TextAlign.center,
  114. style: const TextStyle(fontSize: 15, height: 1.5),
  115. ),
  116. actions: [
  117. const Divider(height: 1),
  118. SizedBox(
  119. width: double.infinity,
  120. child: TextButton(
  121. onPressed: () => Navigator.of(ctx).pop(),
  122. style: TextButton.styleFrom(
  123. foregroundColor: cs.onSurface,
  124. padding: const EdgeInsets.symmetric(vertical: 14),
  125. shape: const RoundedRectangleBorder(
  126. borderRadius: BorderRadius.only(
  127. bottomLeft: Radius.circular(16),
  128. bottomRight: Radius.circular(16),
  129. ),
  130. ),
  131. ),
  132. child: Text(
  133. confirmLabel,
  134. style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600),
  135. ),
  136. ),
  137. ),
  138. ],
  139. );
  140. },
  141. );
  142. }