diff --git a/lib/config/storage_keys.dart b/lib/config/storage_keys.dart index e37881c..f0f79ac 100644 --- a/lib/config/storage_keys.dart +++ b/lib/config/storage_keys.dart @@ -29,7 +29,7 @@ class StorageKeys { static const String themeMode = 'theme_mode'; // 主题模式:light, dark, system // 默认值 - static const String defaultTimerMode = 'random_break'; + static const String defaultTimerMode = 'pomodoro'; static const int defaultFocusTimeMinutes = 90; static const int defaultBigBreakTimeMinutes = 20; static const bool defaultMicroBreakEnabled = true; // 默认启用微休息 diff --git a/lib/page/home/view.dart b/lib/page/home/view.dart index 409189a..0878b0c 100644 --- a/lib/page/home/view.dart +++ b/lib/page/home/view.dart @@ -111,17 +111,17 @@ class HomePage extends StatelessWidget { children: [ _buildModeChip( context: context, - label: '随机提示音', - isSelected: !isPomodoro, + label: '番茄时钟', + isSelected: isPomodoro, enabled: isStopped, - onTap: () => controller.switchTimerMode('random_break'), + onTap: () => controller.switchTimerMode('pomodoro'), ), _buildModeChip( context: context, - label: '番茄时钟', - isSelected: isPomodoro, + label: '随机提示音', + isSelected: !isPomodoro, enabled: isStopped, - onTap: () => controller.switchTimerMode('pomodoro'), + onTap: () => controller.switchTimerMode('random_break'), ), ], ), diff --git a/lib/page/main/controller.dart b/lib/page/main/controller.dart index acb2d1b..1e8a0df 100644 --- a/lib/page/main/controller.dart +++ b/lib/page/main/controller.dart @@ -3,6 +3,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_background_service/flutter_background_service.dart'; import 'package:get/get.dart'; import 'package:time_machine/page/main/state.dart'; +import 'package:time_machine/utils/toast_util.dart'; class MainController extends GetxController { final MainState state = MainState(); @@ -44,14 +45,7 @@ class MainController extends GetxController { state.lastBackPressTime = now; // 显示Toast提示 - Get.snackbar( - '提示', - '再按一次返回键退出应用', - snackPosition: SnackPosition.TOP, - duration: const Duration(seconds: 2), - margin: const EdgeInsets.all(16), - barBlur: 100, - ); + ToastUtil.show('提示', '再按一次返回键退出应用'); return false; // 不退出应用 } else { diff --git a/lib/page/setting/controller.dart b/lib/page/setting/controller.dart index ae781f3..38016ae 100644 --- a/lib/page/setting/controller.dart +++ b/lib/page/setting/controller.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:mmkv/mmkv.dart'; import 'package:time_machine/route/route_name.dart'; +import 'package:time_machine/utils/toast_util.dart'; import '../../service/app_storage_service.dart'; import '../../service/permission_service.dart'; @@ -107,13 +108,7 @@ class SettingController extends GetxController { /// 保存设置到存储 void saveSettings() { if (!state.validateAll()) { - Get.snackbar( - '验证失败', - '请检查输入的设置值', - snackPosition: SnackPosition.TOP, - barBlur: 100, - duration: Duration(seconds: 2) - ); + ToastUtil.show('验证失败', '请检查输入的设置值'); return; } @@ -131,13 +126,7 @@ class SettingController extends GetxController { _storage.encodeInt(StorageKeys.pomodoroLongBreakMinutes, state.pomodoroLongBreakMinutes.value); _storage.encodeInt(StorageKeys.pomodoroLongBreakInterval, state.pomodoroLongBreakInterval.value); - Get.snackbar( - '设置已保存', - '所有设置已成功保存', - snackPosition: SnackPosition.TOP, - barBlur: 100.0, - duration: Duration(seconds: 1), - ); + ToastUtil.show('设置已保存', '所有设置已成功保存'); // 通知 HomeController 重新加载设置 try { @@ -167,13 +156,7 @@ class SettingController extends GetxController { state.updateControllers(); state.clearErrors(); - Get.snackbar( - '已重置', - '所有设置已重置为默认值', - snackPosition: SnackPosition.TOP, - barBlur: 100.0, - duration: Duration(seconds: 1), - ); + ToastUtil.show('已重置', '所有设置已重置为默认值'); } /// 更新专注时间 @@ -268,12 +251,10 @@ class SettingController extends GetxController { // 申请存储权限 final perm = Get.find(); if (!await perm.requestStorage()) { - Get.snackbar( + ToastUtil.show( '权限不足', '备份需要存储权限, 请在「权限管理→存储权限」中授予后再试', - snackPosition: SnackPosition.TOP, - barBlur: 100, - duration: const Duration(seconds: 3), + toastLength: Toast.LENGTH_LONG, ); return; } @@ -288,30 +269,26 @@ class SettingController extends GetxController { final backupPath = await backupRestoreDBService.backupData(); if (backupPath != null) { - Get.snackbar( + // 原生 Toast 对超长无空格路径会截断,只展示外部存储根(/0/)之后的关键路径 + final shownPath = backupPath.split('/0/').last; + ToastUtil.show( '备份成功', - '数据已备份到: $backupPath', - snackPosition: SnackPosition.TOP, - barBlur: 100.0, - duration: const Duration(seconds: 3), + '数据已备份到: $shownPath', + toastLength: Toast.LENGTH_LONG, ); } else { - Get.snackbar( + ToastUtil.show( '备份失败', '无法创建备份文件, 请检查存储权限', - snackPosition: SnackPosition.TOP, - barBlur: 100.0, - duration: const Duration(seconds: 3), + toastLength: Toast.LENGTH_LONG, ); } } catch (e) { Get.log('备份过程中发生错误: $e'); - Get.snackbar( + ToastUtil.show( '备份失败', '备份过程中发生错误: ${e.toString()}', - snackPosition: SnackPosition.TOP, - barBlur: 100.0, - duration: const Duration(seconds: 3), + toastLength: Toast.LENGTH_LONG, ); } finally { state.isBackupInProgress.value = false; @@ -325,12 +302,10 @@ class SettingController extends GetxController { // 申请存储权限 final perm = Get.find(); if (!await perm.requestStorage()) { - Get.snackbar( + ToastUtil.show( '权限不足', '恢复需要存储权限, 请在「权限管理→存储权限」中授予后再试', - snackPosition: SnackPosition.TOP, - barBlur: 100, - duration: const Duration(seconds: 3), + toastLength: Toast.LENGTH_LONG, ); return; } @@ -364,12 +339,10 @@ class SettingController extends GetxController { final success = await backupRestoreDBService.restoreData(); if (success) { - Get.snackbar( + ToastUtil.show( '恢复成功', '数据已成功恢复, 请重启应用以生效', - snackPosition: SnackPosition.TOP, - barBlur: 100.0, - duration: const Duration(seconds: 3), + toastLength: Toast.LENGTH_LONG, ); // 重新加载设置 @@ -383,22 +356,18 @@ class SettingController extends GetxController { // HomeController 可能还没有初始化, 忽略错误 } } else { - Get.snackbar( + ToastUtil.show( '恢复失败', '无法恢复数据, 请检查备份文件是否有效', - snackPosition: SnackPosition.TOP, - barBlur: 100.0, - duration: const Duration(seconds: 3), + toastLength: Toast.LENGTH_LONG, ); } } catch (e) { Get.log('恢复过程中发生错误: $e'); - Get.snackbar( + ToastUtil.show( '恢复失败', '恢复过程中发生错误: ${e.toString()}', - snackPosition: SnackPosition.TOP, - barBlur: 100.0, - duration: const Duration(seconds: 3), + toastLength: Toast.LENGTH_LONG, ); } finally { state.isRestoreInProgress.value = false; diff --git a/lib/page/setting/state.dart b/lib/page/setting/state.dart index c72ba1e..50cf9f9 100644 --- a/lib/page/setting/state.dart +++ b/lib/page/setting/state.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:time_machine/utils/toast_util.dart'; import '../../config/storage_keys.dart'; class SettingState { @@ -137,13 +138,7 @@ class SettingState { if (microBreakIntervalMinMinutes.value > microBreakIntervalMaxMinutes.value) { microBreakIntervalMinMinutes.value = microBreakIntervalMaxMinutes.value; microBreakIntervalMinController.text = microBreakIntervalMinMinutes.value.toString(); - Get.snackbar( - '设置已调整', - '最小间隔不能大于最大间隔,已调整为相同值', - snackPosition: SnackPosition.TOP, - barBlur: 100, - duration: Duration(seconds: 2), - ); + ToastUtil.show('设置已调整', '最小间隔不能大于最大间隔,已调整为相同值'); } } diff --git a/lib/page/setting/view.dart b/lib/page/setting/view.dart index 381e57b..447b00c 100644 --- a/lib/page/setting/view.dart +++ b/lib/page/setting/view.dart @@ -89,9 +89,19 @@ class SettingPage extends StatelessWidget { child: ListView( padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0), children: [ - // 专注设置组 + // 番茄时钟设置组(始终显示,供用户预先配置) SettingSection( - title: '专注设置', + title: '番茄时钟设置', + children: [ + PomodoroSettings(controller: controller), + ], + ), + + const SizedBox(height: 24), + + // 随机提示音设置组 + SettingSection( + title: '随机提示音设置', children: [ SettingTile( title: '专注时间', @@ -125,34 +135,7 @@ class SettingPage extends StatelessWidget { ), ), const SettingDivider(), - SettingTile( - title: '自动开始下一个', - subtitle: '休息结束后自动开始下一个专注', - trailing: Obx(() => SettingSwitch( - value: state.autoStartNextFocus.value, - onChanged: controller.toggleAutoStartNextFocus, - )), - ), - ], - ), - - const SizedBox(height: 24), - - // 微休息设置组 - SettingSection( - title: '微休息设置', - children: [ - MicroBreakSettings(controller: controller), - ], - ), - - const SizedBox(height: 24), - - // 番茄时钟设置组(始终显示,供用户预先配置) - SettingSection( - title: '番茄时钟设置', - children: [ - PomodoroSettings(controller: controller), + MicroBreakSettings(controller: controller) ], ), @@ -175,12 +158,15 @@ class SettingPage extends StatelessWidget { // 显示设置组 SettingSection( - title: '显示设置', + title: '显示与行为', children: [ SettingTile( - title: '主题模式', - subtitle: '选择应用的外观主题', - trailing: const ThemeSelector(), + title: '自动开始下一个', + subtitle: '休息结束后自动开始下一个专注', + trailing: Obx(() => SettingSwitch( + value: state.autoStartNextFocus.value, + onChanged: controller.toggleAutoStartNextFocus, + )), ), const SettingDivider(), SettingTile( @@ -200,6 +186,12 @@ class SettingPage extends StatelessWidget { onChanged: controller.toggleProgressDirection, )), ), + const SettingDivider(), + SettingTile( + title: '主题模式', + subtitle: '选择应用的外观主题', + trailing: const ThemeSelector(), + ), ], ), diff --git a/lib/page/setting/widgets/data_settings.dart b/lib/page/setting/widgets/data_settings.dart index 6eaa274..58eccc1 100644 --- a/lib/page/setting/widgets/data_settings.dart +++ b/lib/page/setting/widgets/data_settings.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:time_machine/utils/toast_util.dart'; import '../../../service/database_service.dart'; import '../controller.dart'; @@ -102,10 +103,7 @@ class DataSettings extends StatelessWidget { if (confirmed == true) { final databaseService = Get.find(); await databaseService.resetDatabase(); - Get.snackbar('成功', '数据库已重置', - duration: Duration(seconds: 1), - barBlur: 100, - ); + ToastUtil.show('成功', '数据库已重置'); } }, child: const Text('重置'), diff --git a/lib/page/setting/widgets/developer_settings.dart b/lib/page/setting/widgets/developer_settings.dart index 1670104..86f2d38 100644 --- a/lib/page/setting/widgets/developer_settings.dart +++ b/lib/page/setting/widgets/developer_settings.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:time_machine/utils/toast_util.dart'; import 'package:time_machine/page/setting/widgets/setting_divider.dart'; import 'package:time_machine/page/setting/widgets/setting_section.dart'; import 'package:time_machine/page/setting/widgets/setting_tile.dart'; @@ -24,7 +25,7 @@ class DeveloperSettings extends StatelessWidget { onPressed: () async { final testDataController = Get.put(TestDataController()); await testDataController.generateTestData(); - Get.snackbar('成功', '测试数据已生成', duration: Duration(seconds: 1)); + ToastUtil.show('成功', '测试数据已生成'); }, child: const Text('生成'), ), diff --git a/lib/page/setting/widgets/permission_settings.dart b/lib/page/setting/widgets/permission_settings.dart index a48f06d..71f86d3 100644 --- a/lib/page/setting/widgets/permission_settings.dart +++ b/lib/page/setting/widgets/permission_settings.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:permission_handler/permission_handler.dart'; +import 'package:time_machine/utils/toast_util.dart'; import '../../../service/permission_service.dart'; import 'setting_tile.dart'; @@ -75,13 +76,7 @@ class PermissionSettings extends StatelessWidget { } void _showOpenSettingsHint() { - Get.snackbar( - '需要在系统设置中手动开启', - '权限已被永久拒绝, 即将跳转到应用设置', - snackPosition: SnackPosition.TOP, - barBlur: 100, - duration: const Duration(seconds: 2), - ); + ToastUtil.show('需要在系统设置中手动开启', '权限已被永久拒绝, 即将跳转到应用设置'); } } diff --git a/lib/page/sound_settings/controller.dart b/lib/page/sound_settings/controller.dart index c51c59a..a1857e2 100644 --- a/lib/page/sound_settings/controller.dart +++ b/lib/page/sound_settings/controller.dart @@ -6,6 +6,7 @@ import 'package:file_picker/file_picker.dart'; import 'package:get/get.dart'; import 'package:mmkv/mmkv.dart'; import 'package:path/path.dart' as p; +import 'package:time_machine/utils/toast_util.dart'; import '../../config/storage_keys.dart'; import '../../service/app_storage_service.dart'; @@ -130,13 +131,7 @@ class SoundSettingsController extends GetxController { // Android 13+ 需要 READ_MEDIA_AUDIO final granted = await _permissionService.requestAudioRead(); if (!granted) { - Get.snackbar( - '权限不足', - '请授予音频读取权限后重试', - snackPosition: SnackPosition.TOP, - barBlur: 100, - duration: const Duration(seconds: 2), - ); + ToastUtil.show('权限不足', '请授予音频读取权限后重试'); return; } @@ -152,13 +147,7 @@ class SoundSettingsController extends GetxController { final dest = await _customSoundStorage.importCustomSound(eventId, src); if (dest == null) { - Get.snackbar( - '导入失败', - '无法保存自定义音效', - snackPosition: SnackPosition.TOP, - barBlur: 100, - duration: const Duration(seconds: 2), - ); + ToastUtil.show('导入失败', '无法保存自定义音效'); return; } @@ -217,13 +206,7 @@ class SoundSettingsController extends GetxController { } await stopAllPreview(); if (!await File(filePath).exists()) { - Get.snackbar( - '试听失败', - '文件不存在', - snackPosition: SnackPosition.TOP, - barBlur: 100, - duration: const Duration(seconds: 2), - ); + ToastUtil.show('试听失败', '文件不存在'); return; } state.previewingTag.value = tag; diff --git a/lib/utils/toast_util.dart b/lib/utils/toast_util.dart new file mode 100644 index 0000000..6cb46c1 --- /dev/null +++ b/lib/utils/toast_util.dart @@ -0,0 +1,25 @@ +import 'package:fluttertoast/fluttertoast.dart'; + +// 重新导出 Toast,使调用方仅 import 本文件即可使用 Toast.LENGTH_SHORT/LONG。 +export 'package:fluttertoast/fluttertoast.dart' show Toast; + +/// 统一 Toast 封装,替代原 Get.snackbar。 +/// +/// 使用原生 fluttertoast:标题与正文以两行展示。 +/// 时长仅 SHORT/LONG 两档,通过 [toastLength] 指定,默认 [Toast.LENGTH_SHORT]。 +/// 注意:Android 11+ 系统会忽略原生 Toast 的位置(gravity)与自定义样式。 +class ToastUtil { + ToastUtil._(); + + static void show( + String title, + String message, { + Toast toastLength = Toast.LENGTH_SHORT, + }) { + Fluttertoast.showToast( + msg: title.isEmpty ? message : '$title\n$message', + toastLength: toastLength, + gravity: ToastGravity.TOP, + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 7d1c77f..10bcdb5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -336,6 +336,14 @@ packages: description: flutter source: sdk version: "0.0.0" + fluttertoast: + dependency: "direct main" + description: + name: fluttertoast + sha256: "7903c9d5339173497bfecbc23bc4212f5a87e0edfac2e1693fb74465ea67da7e" + url: "https://pub.flutter-io.cn" + source: hosted + version: "9.1.0" get: dependency: "direct main" description: @@ -895,4 +903,4 @@ packages: version: "3.1.3" sdks: dart: ">=3.11.0 <4.0.0" - flutter: ">=3.38.4" + flutter: ">=3.41.0" diff --git a/pubspec.yaml b/pubspec.yaml index afdddb4..8d77638 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -43,6 +43,7 @@ dependencies: file_picker: ^12.0.0-beta.5 path_provider: ^2.1.5 permission_handler: ^12.0.0+1 + fluttertoast: ^9.1.0 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons.