Compare commits

...

11 Commits

Author SHA1 Message Date
a2ae3ad55e feat:增加日志记录 2025-11-25 15:41:29 +08:00
70f1e609dd feat:增加通知库的版本 2025-11-25 14:14:02 +08:00
1a5efb1b48 fix:修复重启设备后没有通知提醒的问题 2025-11-24 11:58:14 +08:00
607b27d54f feat:增加common框架 2025-11-23 20:17:10 +08:00
8795279d8a fix:修复重新刷新提醒的逻辑错误 2025-11-21 15:12:16 +08:00
538151ca88 feat:更新提示文字信息 2025-11-21 15:03:00 +08:00
f133e5ce13 feat:更新主题色兼容 2025-11-21 14:58:11 +08:00
3ea162f33e feat:更新颜色模块 2025-11-20 23:40:06 +08:00
6ed9efe06d feat:增加刷新日程提醒功能 2025-11-20 14:01:43 +08:00
b2260d139b feat:增加待办排序UI功能 2025-11-20 11:47:35 +08:00
565b04e65d feat:精简颜色主题 2025-11-20 11:08:21 +08:00
32 changed files with 1219 additions and 2034 deletions

View File

@@ -78,7 +78,7 @@ android {
dependencies { dependencies {
// 添加核心库脱糖依赖 // 添加核心库脱糖依赖
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4") coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
} }
flutter { flutter {

View File

@@ -1,12 +1,17 @@
import 'package:flisp_app/provider/app_provider.dart'; import 'package:flisp_app/provider/app_provider.dart';
import 'package:flisp_app/utils/theme_utils.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_common/flutter_common.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class AppDrawer extends StatelessWidget { class AppDrawer extends StatefulWidget {
const AppDrawer({super.key}); const AppDrawer({super.key});
DrawerHeader _buildDrawerHeader(BuildContext context) { @override
State<AppDrawer> createState() => AppDrawerState();
}
class AppDrawerState extends State<AppDrawer> {
DrawerHeader _buildDrawerHeader() {
return DrawerHeader( return DrawerHeader(
decoration: BoxDecoration(color: Theme.of(context).colorScheme.primary), decoration: BoxDecoration(color: Theme.of(context).colorScheme.primary),
child: Column( child: Column(
@@ -39,42 +44,8 @@ class AppDrawer extends StatelessWidget {
); );
} }
Widget _buildDarkModeSection(BuildContext context, AppProvider appProvider) {
return ListTile(
leading: Icon(
appProvider.isDarkMode ? Icons.dark_mode : Icons.light_mode,
color: Theme.of(context).colorScheme.primary,
),
title: const Text('暗黑模式'),
trailing: Switch(
value: appProvider.isDarkMode,
onChanged: (value) {
appProvider.toggleDarkMode(value);
},
),
onTap: () {
appProvider.toggleDarkMode(!appProvider.isDarkMode);
},
);
}
Widget _buildThemeSection(BuildContext context, AppProvider appProvider) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
child: Text('主题颜色', style: Theme.of(context).textTheme.titleMedium),
),
_buildDarkModeSection(context, appProvider),
buildThemeColorList(context, appProvider),
],
);
}
// 构建抽屉菜单项 // 构建抽屉菜单项
Widget _buildDrawerItem({ Widget _buildDrawerItem({
required BuildContext context,
required IconData icon, required IconData icon,
required String title, required String title,
required VoidCallback onTap, required VoidCallback onTap,
@@ -118,11 +89,10 @@ class AppDrawer extends StatelessWidget {
child: ListView( child: ListView(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
children: [ children: [
_buildDrawerHeader(context), _buildDrawerHeader(),
_buildThemeSection(context, appProvider), ThemeLayout(),
const Divider(), const Divider(),
_buildDrawerItem( _buildDrawerItem(
context: context,
icon: Icons.help, icon: Icons.help,
title: '帮助与反馈', title: '帮助与反馈',
onTap: () { onTap: () {
@@ -136,7 +106,7 @@ class AppDrawer extends StatelessWidget {
_buildVersionInfo( _buildVersionInfo(
context: context, context: context,
fullVersion: appProvider.fullVersion, fullVersion: appProvider.fullVersion,
) ),
], ],
), ),
); );

View File

@@ -3,23 +3,30 @@ import 'package:flisp_app/layout/app_drawer.dart';
import 'package:flisp_app/layout/app_floating_button.dart'; import 'package:flisp_app/layout/app_floating_button.dart';
import 'package:flisp_app/layout/app_navbar.dart'; import 'package:flisp_app/layout/app_navbar.dart';
import 'package:flisp_app/provider/app_provider.dart'; import 'package:flisp_app/provider/app_provider.dart';
import 'package:flisp_app/utils/notify_utils.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class MainScreen extends StatefulWidget { class Home extends StatefulWidget {
const MainScreen({super.key}); const Home({super.key});
@override @override
State<MainScreen> createState() => _MainScreenState(); State<Home> createState() => _HomeState();
} }
class _MainScreenState extends State<MainScreen> { class _HomeState extends State<Home> {
final GlobalKey<AppBodyState> _appBodyKey = GlobalKey(); final GlobalKey<AppBodyState> _appBodyKey = GlobalKey();
final NotifyService notifyService = NotifyService();
void _onPressedFloatingButton(int index) { void _onPressedFloatingButton(int index) {
_appBodyKey.currentState?.showPageAddDialog(index); _appBodyKey.currentState?.showPageAddDialog(index);
} }
@override
void initState() {
super.initState();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final provider = context.watch<AppProvider>(); final provider = context.watch<AppProvider>();

View File

@@ -5,17 +5,25 @@ import 'package:flisp_app/provider/calendar_provider.dart';
import 'package:flisp_app/provider/flisp_provider.dart'; import 'package:flisp_app/provider/flisp_provider.dart';
import 'package:flisp_app/utils/notify_utils.dart'; import 'package:flisp_app/utils/notify_utils.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_common/flutter_common.dart';
import 'package:flutter_common/utils/log_utils.dart';
import 'package:flutter_common/utils/sp_utils.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:hive_flutter/adapters.dart'; import 'package:hive_flutter/adapters.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:syncfusion_localizations/syncfusion_localizations.dart'; import 'package:syncfusion_localizations/syncfusion_localizations.dart';
import 'layout/main_screen.dart'; import 'layout/home.dart';
import 'models/todo.dart'; import 'models/todo.dart';
import 'provider/todo_provider.dart'; import 'provider/todo_provider.dart';
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
// 初始化存储
await SPUtil.init();
// 初始化日志
await initLogger();
// 初始化提醒服务 // 初始化提醒服务
final notifyService = NotifyService(); final notifyService = NotifyService();
await notifyService.initialize(); await notifyService.initialize();
@@ -44,8 +52,18 @@ void main() async {
// calendarBox.clear(); // calendarBox.clear();
// notifyService.cancelAllNotifications(); // notifyService.cancelAllNotifications();
runApp(
runApp(const MyApp()); MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => AppProvider()),
ChangeNotifierProvider(create: (_) => ThemeProvider()),
ChangeNotifierProvider(create: (_) => TodoProvider()),
ChangeNotifierProvider(create: (_) => FlispProvider()),
ChangeNotifierProvider(create: (_) => CalendarProvider()),
],
child: MyApp(),
),
);
} }
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
@@ -53,15 +71,8 @@ class MyApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MultiProvider( final themeProvider = context.watch<ThemeProvider>();
providers: [
ChangeNotifierProvider(create: (_) => AppProvider()),
ChangeNotifierProvider(create: (_) => TodoProvider()),
ChangeNotifierProvider(create: (_) => FlispProvider()),
ChangeNotifierProvider(create: (_) => CalendarProvider()),
],
child: Consumer<AppProvider>(
builder: (context, appProvider, child) {
return MaterialApp( return MaterialApp(
title: '闪灵', title: '闪灵',
localizationsDelegates: [ localizationsDelegates: [
@@ -72,12 +83,9 @@ class MyApp extends StatelessWidget {
], ],
supportedLocales: [const Locale('zh'), const Locale('zh', 'CN')], supportedLocales: [const Locale('zh'), const Locale('zh', 'CN')],
locale: Locale('zh', 'CN'), locale: Locale('zh', 'CN'),
theme: appProvider.currentThemeData, theme: themeProvider.currentThemeData,
home: const MainScreen(), home: const Home(),
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
); );
},
),
);
} }
} }

View File

@@ -111,3 +111,5 @@ enum TodoTab {
const TodoTab(this.label); const TodoTab(this.label);
} }
enum TodoSortMode { time, priority }

View File

@@ -2,10 +2,10 @@ import 'package:flisp_app/models/calendar.dart';
import 'package:flisp_app/provider/calendar_provider.dart'; import 'package:flisp_app/provider/calendar_provider.dart';
import 'package:flisp_app/service/calendar_service.dart'; import 'package:flisp_app/service/calendar_service.dart';
import 'package:flisp_app/utils/notify_utils.dart'; import 'package:flisp_app/utils/notify_utils.dart';
import 'package:flisp_app/widgets/awesome_dialog.dart';
import 'package:flisp_app/widgets/calendar_form.dart'; import 'package:flisp_app/widgets/calendar_form.dart';
import 'package:flisp_app/widgets/calendar_widget.dart'; import 'package:flisp_app/widgets/calendar_widget.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_common/widget/dialog_widget.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:syncfusion_flutter_calendar/calendar.dart'; import 'package:syncfusion_flutter_calendar/calendar.dart';
@@ -107,9 +107,9 @@ class CalendarPageState extends State<CalendarPage> {
}); });
if (isSuccess) { if (isSuccess) {
showSuccessDialog(context, isEditing ? '更新成功' : '添加成功'); showSuccessTip(context, isEditing ? '更新成功' : '添加成功');
} else { } else {
showErrorDialog(context, isEditing ? '更新失败' : '添加失败'); showErrorTip(context, isEditing ? '更新失败' : '添加失败');
} }
} }
@@ -122,9 +122,9 @@ class CalendarPageState extends State<CalendarPage> {
}); });
if (isSuccess) { if (isSuccess) {
showSuccessDialog(context, '删除成功'); showSuccessTip(context, '删除成功');
} else { } else {
showErrorDialog(context, '删除失败'); showErrorTip(context, '删除失败');
} }
} }

View File

@@ -2,10 +2,10 @@ import 'package:flisp_app/models/flisp.dart';
import 'package:flisp_app/provider/flisp_provider.dart'; import 'package:flisp_app/provider/flisp_provider.dart';
import 'package:flisp_app/service/flisp_service.dart'; import 'package:flisp_app/service/flisp_service.dart';
import 'package:flisp_app/utils/flisp_utils.dart'; import 'package:flisp_app/utils/flisp_utils.dart';
import 'package:flisp_app/widgets/awesome_dialog.dart';
import 'package:flisp_app/widgets/flisp_form.dart'; import 'package:flisp_app/widgets/flisp_form.dart';
import 'package:flisp_app/widgets/flisp_widget.dart'; import 'package:flisp_app/widgets/flisp_widget.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_common/widget/dialog_widget.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:flisp_app/widgets/common.dart'; import 'package:flisp_app/widgets/common.dart';
@@ -117,9 +117,9 @@ class FlispPageState extends State<FlispPage> {
}); });
if (isSuccess) { if (isSuccess) {
showSuccessDialog(context, isEditing ? '更新成功' : '添加成功'); showSuccessTip(context, isEditing ? '更新成功' : '添加成功');
} else { } else {
showErrorDialog(context, isEditing ? '更新失败' : '添加失败'); showErrorTip(context, isEditing ? '更新失败' : '添加失败');
} }
} }
@@ -131,9 +131,9 @@ class FlispPageState extends State<FlispPage> {
}); });
if (isSuccess) { if (isSuccess) {
showSuccessDialog(context, '删除成功'); showSuccessTip(context, '删除成功');
} else { } else {
showErrorDialog(context, '删除失败'); showErrorTip(context, '删除失败');
} }
} }
} }

View File

@@ -2,13 +2,13 @@ import 'package:flisp_app/models/flisp.dart';
import 'package:flisp_app/models/todo.dart'; import 'package:flisp_app/models/todo.dart';
import 'package:flisp_app/service/flisp_service.dart'; import 'package:flisp_app/service/flisp_service.dart';
import 'package:flisp_app/service/todo_service.dart'; import 'package:flisp_app/service/todo_service.dart';
import 'package:flisp_app/widgets/chart.dart';
import 'package:flisp_app/widgets/common.dart';
import 'package:flisp_app/widgets/flisp_widget.dart'; import 'package:flisp_app/widgets/flisp_widget.dart';
import 'package:flisp_app/widgets/stats.dart';
import 'package:flisp_app/widgets/todo_widget.dart'; import 'package:flisp_app/widgets/todo_widget.dart';
import 'package:flisp_app/widgets/year_selector.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_common/models/common_model.dart';
import 'package:flutter_common/widget/chart.dart';
import 'package:flutter_common/widget/common_widget.dart';
import 'package:flutter_common/widget/year_selector.dart';
class StatsPage extends StatefulWidget { class StatsPage extends StatefulWidget {
const StatsPage({super.key}); const StatsPage({super.key});
@@ -124,45 +124,7 @@ class StatsPageState extends State<StatsPage> {
], ],
), ),
const SizedBox(height: 5), const SizedBox(height: 5),
Expanded(child: buildFlispStatsCard(allFlisps)), buildFlispStatsCard(context, allFlisps),
],
);
}
Widget _buildFlispMonthlyStats(BuildContext context) {
return Column(
children: [
buildChartTitle(context, '每月闪灵数量统计', Icons.show_chart),
const SizedBox(height: 3),
buildChartDivider(context),
const SizedBox(height: 3),
Expanded(
child: lineChart(
context: context,
xAxisName: '月份',
yAxisName: '数量',
unit: '',
data: flispMonthlyStats,
),
),
],
);
}
Widget _buildFlispCategoryStats(BuildContext context) {
return Column(
children: [
buildChartTitle(context, '闪灵分类统计', Icons.pie_chart),
const SizedBox(height: 3),
buildChartDivider(context),
const SizedBox(height: 3),
Expanded(
child: pieChart(
context: context,
unit: '',
data: flispCategoryStats,
),
),
], ],
); );
} }
@@ -179,44 +141,7 @@ class StatsPageState extends State<StatsPage> {
], ],
), ),
const SizedBox(height: 5), const SizedBox(height: 5),
Expanded(child: buildTodoStatsCard(allTodos)), Expanded(child: buildTodoStatsCard(context, allTodos)),
],
);
}
Widget _buildTodoMonthlyStats(BuildContext context) {
return Column(
children: [
buildChartTitle(context, '每月待办数量统计', Icons.bar_chart),
const SizedBox(height: 3),
buildChartDivider(context),
const SizedBox(height: 3),
Expanded(
child: doubleBarChart(
context: context,
xAxisName: '月份',
yAxisName: '数量',
unit: '',
data1: todoMonthlyStats,
data2: todoMonthlyCompleteStats,
series1Name: '总数',
series2Name: '完成数',
),
),
],
);
}
Widget _buildTodoPriorityStats(BuildContext context) {
return Column(
children: [
buildChartTitle(context, '待办分类统计', Icons.pie_chart),
const SizedBox(height: 3),
buildChartDivider(context),
const SizedBox(height: 3),
Expanded(
child: pieChart(context: context, unit: '', data: todoPriorityStats),
),
], ],
); );
} }
@@ -227,9 +152,7 @@ class StatsPageState extends State<StatsPage> {
child: Column( child: Column(
children: [ children: [
YearSelector( YearSelector(
initialYear: DateTime.now().year, currentYear: DateTime.now().year,
minYear: 2000,
maxYear: 2100,
onYearChanged: onYearChanged:
(year) => { (year) => {
setState(() { setState(() {
@@ -244,22 +167,53 @@ class StatsPageState extends State<StatsPage> {
SizedBox(height: 10), SizedBox(height: 10),
SizedBox( SizedBox(
height: chartHeight, height: chartHeight,
child: BuildCard(child: _buildFlispMonthlyStats(context)), child: CommonCard(
child: LineChart(
title: '每月闪灵数量统计',
xAxisName: '月份',
yAxisName: '数量',
unit: '',
data: flispMonthlyStats,
),
),
), ),
SizedBox(height: 10), SizedBox(height: 10),
SizedBox( SizedBox(
height: chartHeight, height: chartHeight,
child: BuildCard(child: _buildFlispCategoryStats(context)), child: CommonCard(
child: PieChart(
title: '闪灵分类统计',
unit: '',
data: flispCategoryStats,
),
),
), ),
SizedBox(height: 10), SizedBox(height: 10),
SizedBox( SizedBox(
height: chartHeight, height: chartHeight,
child: BuildCard(child: _buildTodoMonthlyStats(context)), child: CommonCard(
child: DoubleBarChart(
title: '每月待办数量统计',
xAxisName: '月份',
yAxisName: '数量',
unit: '',
data1: todoMonthlyStats,
data2: todoMonthlyCompleteStats,
series1Name: '总数',
series2Name: '完成数',
),
),
), ),
SizedBox(height: 10), SizedBox(height: 10),
SizedBox( SizedBox(
height: chartHeight, height: chartHeight,
child: BuildCard(child: _buildTodoPriorityStats(context)), child: CommonCard(
child: PieChart(
title: '待办分类统计',
unit: '',
data: todoPriorityStats,
),
),
), ),
], ],
), ),

View File

@@ -1,9 +1,9 @@
import 'package:flisp_app/provider/todo_provider.dart'; import 'package:flisp_app/provider/todo_provider.dart';
import 'package:flisp_app/service/todo_service.dart'; import 'package:flisp_app/service/todo_service.dart';
import 'package:flisp_app/utils/notify_utils.dart'; import 'package:flisp_app/utils/notify_utils.dart';
import 'package:flisp_app/widgets/awesome_dialog.dart';
import 'package:flisp_app/widgets/todo_widget.dart'; import 'package:flisp_app/widgets/todo_widget.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_common/widget/dialog_widget.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:flisp_app/models/todo.dart'; import 'package:flisp_app/models/todo.dart';
@@ -26,6 +26,7 @@ class TodoPageState extends State<TodoPage> {
late List<Todo> _todos; late List<Todo> _todos;
TodoTab _currentTab = TodoTab.active; TodoTab _currentTab = TodoTab.active;
late TodoSortMode _selectedMode = TodoSortMode.time;
// 获取过滤后的待办事项 // 获取过滤后的待办事项
List<Todo> get _activeTodos => getActiveTodos(_currentTab, _todos); List<Todo> get _activeTodos => getActiveTodos(_currentTab, _todos);
@@ -37,7 +38,13 @@ class TodoPageState extends State<TodoPage> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_todos = todoService.getAllTodos(); _refreshTodos();
}
void _refreshTodos() {
setState(() {
_todos = todoService.getAllTodos(_selectedMode);
});
} }
@override @override
@@ -45,6 +52,8 @@ class TodoPageState extends State<TodoPage> {
return Column( return Column(
children: [ children: [
_buildTodoTabs(), _buildTodoTabs(),
_buildTodoSegment(),
SizedBox(height: 6),
Expanded(child: _buildActiveTodoList(context)), Expanded(child: _buildActiveTodoList(context)),
], ],
); );
@@ -62,6 +71,36 @@ class TodoPageState extends State<TodoPage> {
); );
} }
Widget _buildTodoSegment() {
final colors = Theme.of(context).colorScheme;
return SegmentedButton<TodoSortMode>(
showSelectedIcon: false,
emptySelectionAllowed: false,
multiSelectionEnabled: false,
segments: [
ButtonSegment<TodoSortMode>(
value: TodoSortMode.time,
icon: Icon(Icons.access_time_filled, size: 16),
label: const Text('时间', style: TextStyle(fontSize: 12)),
),
ButtonSegment<TodoSortMode>(
value: TodoSortMode.priority,
icon: Icon(Icons.flag_outlined, size: 16),
label: const Text('优先级', style: TextStyle(fontSize: 12)),
),
],
selected: {_selectedMode},
onSelectionChanged: (Set<TodoSortMode> newSelection) {
setState(() {
_selectedMode = newSelection.first;
});
_refreshTodos();
},
style: buildSegmentStyle(colors),
);
}
Widget _buildActiveTodoList(BuildContext context) { Widget _buildActiveTodoList(BuildContext context) {
return _activeTodos.isEmpty return _activeTodos.isEmpty
? buildEmptyState(context, _currentTab) ? buildEmptyState(context, _currentTab)
@@ -84,6 +123,8 @@ class TodoPageState extends State<TodoPage> {
// 切换待办事项完成状态 // 切换待办事项完成状态
void _toggleTodo(Todo todo) async { void _toggleTodo(Todo todo) async {
final bool? result = await showConfirmDialog(context, '确定已完成该待办吗?');
if (result == true) {
await notifyService.cancelNotification(todo.id); await notifyService.cancelNotification(todo.id);
setState(() { setState(() {
@@ -92,6 +133,7 @@ class TodoPageState extends State<TodoPage> {
todoService.updateTodo(todo); todoService.updateTodo(todo);
}); });
} }
}
// 显示对话框 // 显示对话框
void _showDialog(bool isEditing, Todo? todo) { void _showDialog(bool isEditing, Todo? todo) {
@@ -142,14 +184,12 @@ class TodoPageState extends State<TodoPage> {
); );
} }
setState(() { _refreshTodos();
_todos = todoService.getAllTodos();
});
if (isSuccess) { if (isSuccess) {
showSuccessDialog(context, isEditing ? '更新成功' : '添加成功'); showSuccessTip(context, isEditing ? '更新成功' : '添加成功');
} else { } else {
showErrorDialog(context, isEditing ? '更新失败' : '添加失败'); showErrorTip(context, isEditing ? '更新失败' : '添加失败');
} }
} }
@@ -157,14 +197,12 @@ class TodoPageState extends State<TodoPage> {
await notifyService.cancelNotification(todo.id); await notifyService.cancelNotification(todo.id);
bool isSuccess = await todoService.deleteTodo(todo); bool isSuccess = await todoService.deleteTodo(todo);
setState(() { _refreshTodos();
_todos = todoService.getAllTodos();
});
if (isSuccess) { if (isSuccess) {
showSuccessDialog(context, '删除成功'); showSuccessTip(context, '删除成功');
} else { } else {
showErrorDialog(context, '删除失败'); showErrorTip(context, '删除失败');
} }
} }
} }

View File

@@ -1,71 +1,14 @@
import 'package:flisp_app/utils/theme_utils.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
import 'package:shared_preferences/shared_preferences.dart';
class AppProvider with ChangeNotifier { class AppProvider with ChangeNotifier {
static const String _themeKey = 'selected_theme';
static const String _darkModeKey = 'is_dark_mode';
static const List<String> appBarTitles = ['闪灵', '待办', '日程', '统计']; static const List<String> appBarTitles = ['闪灵', '待办', '日程', '统计'];
int currentTab = 0; int currentTab = 0;
String currentAppBarTitle = '闪灵'; String currentAppBarTitle = '闪灵';
bool isDarkMode = false;
ThemeColor currentTheme = defaultThemes[0];
String appVersion = '1.0.0'; String appVersion = '1.0.0';
String buildNumber = '1'; String buildNumber = '1';
late SharedPreferences _prefs;
bool isInitialized = false;
AppProvider() {
_initPreferences();
}
// 初始化 SharedPreferences
Future<void> _initPreferences() async {
_prefs = await SharedPreferences.getInstance();
_loadPreferences();
await _getVersionInfo();
isInitialized = true;
notifyListeners();
}
// 加载存储的设置
void _loadPreferences() {
isDarkMode = _prefs.getBool(_darkModeKey) ?? false;
// 加载主题色
final themeName = _prefs.getString(_themeKey);
if (themeName != null) {
currentTheme = getThemeColor(themeName);
}
}
Future<void> _getVersionInfo() async {
try {
PackageInfo packageInfo = await PackageInfo.fromPlatform();
appVersion = packageInfo.version;
buildNumber = packageInfo.buildNumber;
notifyListeners();
} catch (e) {
print('获取版本信息失败: $e');
}
}
// 保存主题色
Future<void> _saveTheme() async {
await _prefs.setString(_themeKey, currentTheme.name);
}
// 保存暗黑模式
Future<void> _saveDarkMode() async {
await _prefs.setBool(_darkModeKey, isDarkMode);
}
List<ThemeColor> get availableThemes => defaultThemes;
String get fullVersion => '$appVersion+$buildNumber'; String get fullVersion => '$appVersion+$buildNumber';
void changeTab(int index) { void changeTab(int index) {
@@ -73,30 +16,4 @@ class AppProvider with ChangeNotifier {
currentAppBarTitle = appBarTitles[index]; currentAppBarTitle = appBarTitles[index];
notifyListeners(); notifyListeners();
} }
// 切换明暗模式
void toggleDarkMode(bool value) {
isDarkMode = value;
_saveDarkMode();
notifyListeners();
}
// 更改主题色
void changeTheme(ThemeColor theme) {
currentTheme = theme;
_saveTheme();
notifyListeners();
}
ThemeData get currentThemeData {
return ThemeData(
primarySwatch: currentTheme.materialColor,
colorScheme: ColorScheme.fromSeed(
seedColor: currentTheme.primaryColor,
brightness: isDarkMode ? Brightness.dark : Brightness.light,
),
useMaterial3: true,
fontFamily: 'CustomFont',
);
}
} }

View File

@@ -15,9 +15,29 @@ class TodoService {
} }
} }
List<Todo> getAllTodos() { List<Todo> getAllTodos(TodoSortMode mode) {
if (mode == TodoSortMode.time) {
return box.values.toList() return box.values.toList()
..sort((a, b) => b.updateTime.compareTo(a.updateTime)); ..sort((a, b) {
// 先按是否有 dueDate 分组:有时间的排前面
if (a.dueDate != null && b.dueDate == null) {
return -1; // a有时间b没有时间a排在前面
} else if (a.dueDate == null && b.dueDate != null) {
return 1; // a没有时间b有时间b排在前面
}
// 都有 dueDate按 dueDate 排序
else if (a.dueDate != null && b.dueDate != null) {
return a.dueDate!.compareTo(b.dueDate!); // 最近的在前面
}
// 都没有 dueDate按 updateTime 排序
else {
return b.updateTime.compareTo(a.updateTime); // 最近的在前面
}
});
} else {
return box.values.toList()
..sort((a, b) => b.priorityIndex.compareTo(a.priorityIndex));
}
} }
List<Todo> getAllTodosByYear(int year) { List<Todo> getAllTodosByYear(int year) {

View File

@@ -1,9 +0,0 @@
import 'package:intl/intl.dart';
String formatDate(DateTime date) {
return DateFormat('yyyy-MM-dd').format(date);
}
String formatTime(DateTime datetime) {
return DateFormat('yyyy-MM-dd HH:mm:ss').format(datetime);
}

View File

@@ -1,57 +0,0 @@
import 'dart:io';
import 'package:crypto/crypto.dart';
import 'package:file_picker/file_picker.dart';
import 'package:minio/io.dart';
import 'package:minio/minio.dart';
class MinIOHelper {
static final MinIOHelper _instance = MinIOHelper._internal();
factory MinIOHelper() => _instance;
final String rustfsIp = '14.103.235.151';
final String rustfsFileUrl = 'http://14.103.235.151:9100';
final String bucketName = 'flisp';
MinIOHelper._internal() {
_minio = Minio(
endPoint: rustfsIp,
port: 9100,
accessKey: "tHSFfcDW8qpCzKa2Xg6Y",
secretKey: "oq79EeYJ4jdczRp2IHUMCnbKtSw58NgDlG3sOkvX",
useSSL: false,
);
}
late Minio _minio;
Future<String> uploadFile({
required PlatformFile file,
Function(double)? onProgress,
}) async {
try {
String hashName = await _generateMD5HashName(file.path!);
String fileName = '$hashName${_getFileExtension(file.name)}';
await _minio.fPutObject(bucketName, fileName, file.path!);
return fileName;
} catch (e) {
throw Exception('文件上传失败: $e');
}
}
String _getFileExtension(String fileName) {
if (fileName.contains('.')) {
return '.${fileName.split('.').last.toLowerCase()}';
}
return '';
}
Future<String> _generateMD5HashName(String filePath) async {
final file = File(filePath);
final bytes = await file.readAsBytes();
final hash = md5.convert(bytes);
return hash.toString();
}
}

View File

@@ -1,3 +1,10 @@
import 'package:flisp_app/models/calendar.dart';
import 'package:flisp_app/models/todo.dart';
import 'package:flisp_app/service/calendar_service.dart';
import 'package:flisp_app/service/todo_service.dart';
import 'package:flutter/material.dart';
import 'package:flutter_common/utils/date_utils.dart';
import 'package:flutter_common/utils/log_utils.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:timezone/data/latest_all.dart' as tz; import 'package:timezone/data/latest_all.dart' as tz;
import 'package:timezone/timezone.dart' as tz; import 'package:timezone/timezone.dart' as tz;
@@ -11,9 +18,6 @@ class NotifyService {
late FlutterLocalNotificationsPlugin _notifications; late FlutterLocalNotificationsPlugin _notifications;
// 使用绝对时间(而不是相对时间),避免时区转换问题。
final dateInterpretation = UILocalNotificationDateInterpretation.absoluteTime;
// Android特殊模式即使设备处于省电模式也能准时触发。 // Android特殊模式即使设备处于省电模式也能准时触发。
final scheduleMode = AndroidScheduleMode.exactAllowWhileIdle; final scheduleMode = AndroidScheduleMode.exactAllowWhileIdle;
@@ -23,7 +27,6 @@ class NotifyService {
// 初始化时区 // 初始化时区
tz.initializeTimeZones(); tz.initializeTimeZones();
// String timeZoneName = await FlutterNativeTimezone.getLocalTimezone();
tz.setLocalLocation(tz.getLocation("Asia/Shanghai")); tz.setLocalLocation(tz.getLocation("Asia/Shanghai"));
// 设置Android平台的初始化配置 使用应用图标作为通知图标 // 设置Android平台的初始化配置 使用应用图标作为通知图标
@@ -45,6 +48,57 @@ class NotifyService {
); );
await _notifications.initialize(settings); await _notifications.initialize(settings);
WidgetsBinding.instance.addPostFrameCallback((_) {
_refreshScheduled();
});
}
/// 重新刷新提醒 防止每次重启设备后提醒丢失的问题
void _refreshScheduled() async {
logger.i('开始刷新提醒');
await cancelAllNotifications();
TodoService todoService = TodoService();
final List<Todo> todoResult = todoService.getAllTodos(
TodoSortMode.priority,
);
final List<Todo> todayTodos =
todoResult
.where(
(todo) => !todo.isCompleted && isAfterToday(todo.scheduledTime),
)
.toList();
for (Todo todo in todayTodos) {
await scheduleNotification(
id: todo.id,
title: '待办提醒',
body: todo.title,
scheduledTime: todo.scheduledTime!,
);
}
logger.i('刷新 ${todayTodos.length}个 待办提醒');
CalendarService calendarService = CalendarService();
final List<Calendar> calendarResult = calendarService.getAllCalendars();
final List<Calendar> todayCalendars =
calendarResult
.where((item) => isAfterToday(item.scheduledTime))
.toList();
for (Calendar calendar in todayCalendars) {
await scheduleNotification(
id: calendar.id,
title: '日程提醒',
body: calendar.title,
scheduledTime: calendar.scheduledTime!,
);
}
logger.i('刷新 ${todayCalendars.length}个 日程提醒');
logger.i('结束刷新提醒');
} }
// 创建Android通知详情 // 创建Android通知详情
@@ -101,9 +155,7 @@ class NotifyService {
body, body,
tz.TZDateTime.from(scheduledTime, tz.local), tz.TZDateTime.from(scheduledTime, tz.local),
_details, _details,
uiLocalNotificationDateInterpretation: androidScheduleMode: scheduleMode,
UILocalNotificationDateInterpretation.absoluteTime,
androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
); );
} }
@@ -112,7 +164,7 @@ class NotifyService {
try { try {
await _notifications.cancel(id); await _notifications.cancel(id);
} catch (e) { } catch (e) {
print('取消通知失败: $e'); logger.i('取消通知失败: $e');
} }
} }

View File

@@ -1,236 +0,0 @@
import 'dart:ui';
import 'package:flisp_app/provider/app_provider.dart';
import 'package:flutter/material.dart';
class ThemeColor {
final String name;
final Color primaryColor;
final MaterialColor materialColor;
ThemeColor({
required this.name,
required this.primaryColor,
required this.materialColor,
});
}
final List<ThemeColor> defaultThemes = [
ThemeColor(
name: '科技蓝',
primaryColor: Color(0xFF2563EB),
materialColor: MaterialColor(0xFF2563EB, {
50: Color(0xFFDBEAFE),
100: Color(0xFFBFDBFE),
200: Color(0xFF93C5FD),
300: Color(0xFF60A5FA),
400: Color(0xFF3B82F6),
500: Color(0xFF2563EB),
600: Color(0xFF1D4ED8),
700: Color(0xFF1E40AF),
800: Color(0xFF1E3A8A),
900: Color(0xFF1E3A8A),
}),
),
ThemeColor(
name: '翡翠绿',
primaryColor: Color(0xFF10B981),
materialColor: MaterialColor(0xFF10B981, {
50: Color(0xFFECFDF5),
100: Color(0xFFD1FAE5),
200: Color(0xFFA7F3D0),
300: Color(0xFF6EE7B7),
400: Color(0xFF34D399),
500: Color(0xFF10B981),
600: Color(0xFF059669),
700: Color(0xFF047857),
800: Color(0xFF065F46),
900: Color(0xFF064E3B),
}),
),
ThemeColor(
name: '活力橙',
primaryColor: Color(0xFFF59E0B),
materialColor: MaterialColor(0xFFF59E0B, {
50: Color(0xFFFFFBEB),
100: Color(0xFFFEF3C7),
200: Color(0xFFFDE68A),
300: Color(0xFFFCD34D),
400: Color(0xFFFBBF24),
500: Color(0xFFF59E0B),
600: Color(0xFFD97706),
700: Color(0xFFB45309),
800: Color(0xFF92400E),
900: Color(0xFF78350F),
}),
),
ThemeColor(
name: '梦幻紫',
primaryColor: Color(0xFF8B5CF6),
materialColor: MaterialColor(0xFF8B5CF6, {
50: Color(0xFFF5F3FF),
100: Color(0xFFEDE9FE),
200: Color(0xFFDDD6FE),
300: Color(0xFFC4B5FD),
400: Color(0xFFA78BFA),
500: Color(0xFF8B5CF6),
600: Color(0xFF7C3AED),
700: Color(0xFF6D28D9),
800: Color(0xFF5B21B6),
900: Color(0xFF4C1D95),
}),
),
ThemeColor(
name: '浪漫粉',
primaryColor: Color(0xFFEC4899),
materialColor: MaterialColor(0xFFEC4899, {
50: Color(0xFFFDF2F8),
100: Color(0xFFFCE7F3),
200: Color(0xFFFBCFE8),
300: Color(0xFFF9A8D4),
400: Color(0xFFF472B6),
500: Color(0xFFEC4899),
600: Color(0xFFDB2777),
700: Color(0xFFBE185D),
800: Color(0xFF9D174D),
900: Color(0xFF831843),
}),
),
ThemeColor(
name: '清新青',
primaryColor: Color(0xFF06B6D4),
materialColor: MaterialColor(0xFF06B6D4, {
50: Color(0xFFF0FDFA),
100: Color(0xFFCCFBF1),
200: Color(0xFF99F6E4),
300: Color(0xFF5EEAD4),
400: Color(0xFF2DD4BF),
500: Color(0xFF06B6D4),
600: Color(0xFF0891B2),
700: Color(0xFF0E7490),
800: Color(0xFF155E75),
900: Color(0xFF164E63),
}),
),
ThemeColor(
name: '深空蓝',
primaryColor: Color(0xFF1E40AF),
materialColor: MaterialColor(0xFF1E40AF, {
50: Color(0xFFEFF6FF),
100: Color(0xFFDBEAFE),
200: Color(0xFFBFDBFE),
300: Color(0xFF93C5FD),
400: Color(0xFF60A5FA),
500: Color(0xFF3B82F6),
600: Color(0xFF2563EB),
700: Color(0xFF1D4ED8),
800: Color(0xFF1E40AF),
900: Color(0xFF1E3A8A),
}),
),
ThemeColor(
name: '落日红',
primaryColor: Color(0xFFEF4444),
materialColor: MaterialColor(0xFFEF4444, {
50: Color(0xFFFEF2F2),
100: Color(0xFFFEE2E2),
200: Color(0xFFFECACA),
300: Color(0xFFFCA5A5),
400: Color(0xFFF87171),
500: Color(0xFFEF4444),
600: Color(0xFFDC2626),
700: Color(0xFFB91C1C),
800: Color(0xFF991B1B),
900: Color(0xFF7F1D1D),
}),
),
];
ThemeColor getThemeColor(String themeName) {
return defaultThemes.firstWhere(
(theme) => theme.name == themeName,
orElse: () => defaultThemes[0],
);
}
Widget buildThemeColorList(BuildContext context, AppProvider appProvider) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
childAspectRatio: 1.2,
),
itemCount: appProvider.availableThemes.length,
itemBuilder: (context, index) {
final themeColor = appProvider.availableThemes[index];
final isSelected = appProvider.currentTheme == themeColor;
return _buildThemeColorItem(
themeColor: themeColor,
isSelected: isSelected,
onTap: () => appProvider.changeTheme(themeColor),
);
},
),
);
}
Widget _buildThemeColorItem({
required ThemeColor themeColor,
required bool isSelected,
required VoidCallback onTap,
}) {
return GestureDetector(
onTap: onTap,
child: Container(
decoration: BoxDecoration(
color:
isSelected
? themeColor.primaryColor.withAlpha(50)
: Colors.transparent,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: isSelected ? themeColor.primaryColor : Colors.grey.shade300,
width: isSelected ? 2 : 1,
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 颜色圆点
Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: themeColor.primaryColor,
shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: 2),
boxShadow: [
BoxShadow(
color: Colors.black.withAlpha(10),
blurRadius: 2,
offset: const Offset(0, 1),
),
],
),
),
const SizedBox(height: 4),
Text(
themeColor.name,
style: TextStyle(
fontSize: 10,
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
color:
isSelected ? themeColor.primaryColor : Colors.grey.shade600,
),
),
],
),
),
);
}

View File

@@ -1,48 +0,0 @@
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
class ToastUtil {
static void success(String message) {
Fluttertoast.showToast(
msg: message,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.TOP,
backgroundColor: Colors.green,
textColor: Colors.white,
fontSize: 16.0,
);
}
static void error(String message) {
Fluttertoast.showToast(
msg: message,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.TOP,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 16.0,
);
}
static void warning(String message) {
Fluttertoast.showToast(
msg: message,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.TOP,
backgroundColor: Colors.orange,
textColor: Colors.white,
fontSize: 16.0,
);
}
static void info(String message) {
Fluttertoast.showToast(
msg: message,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.TOP,
backgroundColor: Colors.blue,
textColor: Colors.white,
fontSize: 16.0,
);
}
}

View File

@@ -25,16 +25,23 @@ List<Todo> getActiveTodos(TodoTab currentTab, List<Todo> todos) {
// 格式化截止日期 // 格式化截止日期
String formatDueDate(DateTime dueDate, bool isToday, bool isTomorrow) { String formatDueDate(DateTime dueDate, bool isToday, bool isTomorrow) {
if (isToday) return '今天截止';
if (isTomorrow) return '明天截止';
final now = DateTime.now(); final now = DateTime.now();
final difference = dueDate.difference(DateTime(now.year, now.month, now.day)); final today = DateTime(now.year, now.month, now.day);
final dueDateStart = DateTime(dueDate.year, dueDate.month, dueDate.day);
final difference = dueDateStart.difference(today);
if (difference.inDays < 7) { // 处理过期情况
return '${difference.inDays}天后截止'; if (difference.inDays < 0) {
final daysPast = -difference.inDays;
return '已过期$daysPast天';
} }
// 处理未来日期
if (difference.inDays == 0) return '今天截止';
if (difference.inDays == 1) return '明天截止';
if (difference.inDays == 2) return '后天截止';
if (difference.inDays < 7) return '${difference.inDays}天后截止';
return DateFormat("MM-dd").format(dueDate); return DateFormat("MM-dd").format(dueDate);
} }

View File

@@ -1,68 +0,0 @@
import 'package:awesome_dialog/awesome_dialog.dart';
import 'package:flutter/material.dart';
void showAwesomeDialog({
required BuildContext context,
required Widget body,
required VoidCallback onOk,
required VoidCallback onCancel,
}) {
AwesomeDialog(
context: context,
dialogType: DialogType.noHeader,
animType: AnimType.scale,
body: body,
dialogBackgroundColor: Theme.of(context).colorScheme.surfaceContainer,
btnOkText: "确认",
btnCancelText: "取消",
btnOkColor: Colors.orange,
btnCancelColor: Colors.grey,
buttonsBorderRadius: BorderRadius.circular(10),
headerAnimationLoop: false,
dismissOnTouchOutside: false,
dismissOnBackKeyPress: true,
btnOk: ElevatedButton(
onPressed: onOk,
style: ElevatedButton.styleFrom(
elevation: 0,
backgroundColor: Theme.of(context).colorScheme.primary,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 6),
textStyle: TextStyle(fontSize: 14, fontWeight: FontWeight.bold, fontFamily: 'CustomFont'),
),
child: Text("确认"),
),
btnCancelOnPress: onCancel,
).show();
}
// 显示成功提示
void showSuccessDialog(BuildContext context, String message) {
AwesomeDialog(
context: context,
dialogType: DialogType.success,
animType: AnimType.scale,
title: message,
btnOkText: "好的",
btnOkColor: Colors.green,
btnOkOnPress: () {},
autoHide: Duration(seconds: 2),
).show();
}
// 显示失败提示
void showErrorDialog(BuildContext context, String message) {
AwesomeDialog(
context: context,
dialogType: DialogType.error,
animType: AnimType.scale,
title: message,
btnOkText: "好的",
btnOkColor: Colors.red,
btnOkOnPress: () {},
autoHide: Duration(seconds: 2),
).show();
}

View File

@@ -1,6 +1,5 @@
import 'package:flisp_app/models/calendar.dart'; import 'package:flisp_app/models/calendar.dart';
import 'package:flisp_app/provider/calendar_provider.dart'; import 'package:flisp_app/provider/calendar_provider.dart';
import 'package:flisp_app/utils/date_utils.dart';
import 'package:flisp_app/widgets/common.dart'; import 'package:flisp_app/widgets/common.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart';
@@ -24,18 +23,16 @@ class CalendarForm extends StatefulWidget {
} }
class _CalendarFormState extends State<CalendarForm> { class _CalendarFormState extends State<CalendarForm> {
final int maxTitleCount = 10;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
} }
@override Widget buildTitle() {
Widget build(BuildContext context) {
final int maxTitleCount = 10;
final provider = Provider.of<CalendarProvider>(context);
final colors = Theme.of(context).colorScheme; final colors = Theme.of(context).colorScheme;
Widget buildTitle() {
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
@@ -67,7 +64,9 @@ class _CalendarFormState extends State<CalendarForm> {
return null; return null;
} }
FormBuilderTextField buildTitleField() { FormBuilderTextField buildTitleField(CalendarProvider provider) {
final colors = Theme.of(context).colorScheme;
return FormBuilderTextField( return FormBuilderTextField(
name: 'title', name: 'title',
initialValue: provider.formItem.title, initialValue: provider.formItem.title,
@@ -80,7 +79,11 @@ class _CalendarFormState extends State<CalendarForm> {
label: RichText( label: RichText(
text: TextSpan( text: TextSpan(
text: '内容', text: '内容',
style: TextStyle(color: Colors.grey.shade700, fontSize: 16), style: TextStyle(
color: Colors.grey,
fontSize: 16,
fontFamily: 'CustomFont',
),
children: const [ children: const [
TextSpan( TextSpan(
text: '*', text: '*',
@@ -110,7 +113,7 @@ class _CalendarFormState extends State<CalendarForm> {
); );
} }
String? startTimeFieldValidator(value) { String? startTimeFieldValidator(CalendarProvider provider, value) {
if (value == null) { if (value == null) {
return '请选择开始时间'; return '请选择开始时间';
} }
@@ -129,7 +132,8 @@ class _CalendarFormState extends State<CalendarForm> {
return null; return null;
} }
FormBuilderDateTimePicker buildStartTimeField() { FormBuilderDateTimePicker buildStartTimeField(CalendarProvider provider) {
final colors = Theme.of(context).colorScheme;
var startTime = provider.formItem.startTime; var startTime = provider.formItem.startTime;
return FormBuilderDateTimePicker( return FormBuilderDateTimePicker(
@@ -143,11 +147,8 @@ class _CalendarFormState extends State<CalendarForm> {
}); });
}, },
decoration: InputDecoration( decoration: InputDecoration(
labelText: labelText: startTime == null ? '选择开始时间' : '开始时间: ',
startTime == null ? '选择开始时间' : '开始时间: ${formatTime(startTime)}', labelStyle: TextStyle(color: Colors.grey),
labelStyle: TextStyle(
color: startTime == null ? Colors.grey : Colors.black87,
),
prefixIcon: Icon(Icons.calendar_month, color: Colors.purple), prefixIcon: Icon(Icons.calendar_month, color: Colors.purple),
border: buildFormBoard(), border: buildFormBoard(),
enabledBorder: buildFormEnabledBoard(), enabledBorder: buildFormEnabledBoard(),
@@ -156,11 +157,11 @@ class _CalendarFormState extends State<CalendarForm> {
fillColor: colors.surface, fillColor: colors.surface,
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 14), contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 14),
), ),
validator: (value) => startTimeFieldValidator(value), validator: (value) => startTimeFieldValidator(provider, value),
); );
} }
String? endTimeFieldValidator(value) { String? endTimeFieldValidator(CalendarProvider provider, value) {
if (value == null) { if (value == null) {
return '请选择结束时间'; return '请选择结束时间';
} }
@@ -179,7 +180,8 @@ class _CalendarFormState extends State<CalendarForm> {
return null; return null;
} }
FormBuilderDateTimePicker buildEndTimeField() { FormBuilderDateTimePicker buildEndTimeField(CalendarProvider provider) {
final colors = Theme.of(context).colorScheme;
var endTime = provider.formItem.endTime; var endTime = provider.formItem.endTime;
return FormBuilderDateTimePicker( return FormBuilderDateTimePicker(
@@ -193,11 +195,8 @@ class _CalendarFormState extends State<CalendarForm> {
}); });
}, },
decoration: InputDecoration( decoration: InputDecoration(
labelText: labelText: endTime == null ? '选择结束时间' : '结束时间: ',
endTime == null ? '选择结束时间' : '结束时间: ${formatTime(endTime)}', labelStyle: TextStyle(color: Colors.grey),
labelStyle: TextStyle(
color: endTime == null ? Colors.grey : Colors.black87,
),
prefixIcon: Icon(Icons.calendar_month, color: Colors.purple), prefixIcon: Icon(Icons.calendar_month, color: Colors.purple),
border: buildFormBoard(), border: buildFormBoard(),
enabledBorder: buildFormEnabledBoard(), enabledBorder: buildFormEnabledBoard(),
@@ -206,11 +205,11 @@ class _CalendarFormState extends State<CalendarForm> {
fillColor: colors.surface, fillColor: colors.surface,
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 14), contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 14),
), ),
validator: (value) => endTimeFieldValidator(value), validator: (value) => endTimeFieldValidator(provider, value),
); );
} }
IconButton buildClearScheduled() { IconButton buildClearScheduled(CalendarProvider provider) {
return IconButton( return IconButton(
icon: Icon(Icons.clear, size: 18), icon: Icon(Icons.clear, size: 18),
onPressed: () { onPressed: () {
@@ -229,7 +228,8 @@ class _CalendarFormState extends State<CalendarForm> {
return null; return null;
} }
FormBuilderDateTimePicker buildScheduledTimeField() { FormBuilderDateTimePicker buildScheduledTimeField(CalendarProvider provider) {
final colors = Theme.of(context).colorScheme;
var scheduledTime = provider.formItem.scheduledTime; var scheduledTime = provider.formItem.scheduledTime;
return FormBuilderDateTimePicker( return FormBuilderDateTimePicker(
@@ -243,13 +243,8 @@ class _CalendarFormState extends State<CalendarForm> {
}); });
}, },
decoration: InputDecoration( decoration: InputDecoration(
labelText: labelText: scheduledTime == null ? '选择提醒时间' : '提醒时间: ',
scheduledTime == null labelStyle: TextStyle(color: Colors.grey),
? '选择提醒日期'
: '提醒: ${formatTime(scheduledTime)}',
labelStyle: TextStyle(
color: scheduledTime == null ? Colors.grey : Colors.black87,
),
prefixIcon: Icon(Icons.calendar_month, color: Colors.purple), prefixIcon: Icon(Icons.calendar_month, color: Colors.purple),
border: buildFormBoard(), border: buildFormBoard(),
enabledBorder: buildFormEnabledBoard(), enabledBorder: buildFormEnabledBoard(),
@@ -257,35 +252,40 @@ class _CalendarFormState extends State<CalendarForm> {
filled: true, filled: true,
fillColor: colors.surface, fillColor: colors.surface,
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 14), contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 14),
suffixIcon: scheduledTime != null ? buildClearScheduled() : null, suffixIcon:
scheduledTime != null ? buildClearScheduled(provider) : null,
), ),
validator: (value) => scheduledTimeFieldValidator(value), validator: (value) => scheduledTimeFieldValidator(value),
); );
} }
FormBuilder buildForm() { FormBuilder buildForm(CalendarProvider provider) {
return FormBuilder( return FormBuilder(
key: widget.formKey, key: widget.formKey,
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
buildTitleField(), buildTitleField(provider),
SizedBox(height: 12), SizedBox(height: 12),
buildStartTimeField(), buildStartTimeField(provider),
SizedBox(height: 12), SizedBox(height: 12),
buildEndTimeField(), buildEndTimeField(provider),
SizedBox(height: 12), SizedBox(height: 12),
buildScheduledTimeField(), buildScheduledTimeField(provider),
], ],
), ),
); );
} }
@override
Widget build(BuildContext context) {
final provider = Provider.of<CalendarProvider>(context);
return Padding( return Padding(
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 10), padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 10),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [buildTitle(), SizedBox(height: 20), buildForm()], children: [buildTitle(), SizedBox(height: 20), buildForm(provider)],
), ),
); );
} }

View File

@@ -2,6 +2,7 @@ import 'package:flisp_app/models/calendar.dart';
import 'package:flisp_app/provider/calendar_provider.dart'; import 'package:flisp_app/provider/calendar_provider.dart';
import 'package:flisp_app/widgets/common.dart'; import 'package:flisp_app/widgets/common.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_common/widget/dialog_widget.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:syncfusion_flutter_calendar/calendar.dart'; import 'package:syncfusion_flutter_calendar/calendar.dart';
@@ -107,7 +108,7 @@ Widget buildCalendar({
onLongPress: (CalendarLongPressDetails details) async { onLongPress: (CalendarLongPressDetails details) async {
if (details.targetElement == CalendarElement.appointment) { if (details.targetElement == CalendarElement.appointment) {
if (details.appointments?.length == 1) { if (details.appointments?.length == 1) {
final bool? result = await showDeleteConfirmationDialog(context); final bool? result = await showConfirmDialog(context, '确定要删除这个项目吗?');
if (result == true) { if (result == true) {
onDelete(details.appointments!.first); onDelete(details.appointments!.first);
} }

View File

@@ -1,291 +0,0 @@
import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_charts/charts.dart';
class ChartData {
final String name;
final double value;
ChartData({required this.name, required this.value});
}
Widget buildChartTitle(BuildContext context, String title, IconData icon) {
return Row(
children: [
Icon(icon, color: Theme.of(context).colorScheme.primary),
Text(title),
],
);
}
Widget buildChartDivider(BuildContext context) {
return Divider(
height: 1,
thickness: 1,
color: Theme.of(context).colorScheme.primary,
indent: 0,
endIndent: 0,
);
}
Widget lineChart({
required BuildContext context,
required String xAxisName,
required String yAxisName,
required String unit,
required List<ChartData> data,
}) {
return SfCartesianChart(
// 图表标题
// title: ChartTitle(text: '2023年上半年销售额万元'),
// X轴配置类别轴
primaryXAxis: CategoryAxis(majorGridLines: MajorGridLines(width: 0)),
// Y轴配置数值轴
// primaryYAxis: NumericAxis(title: AxisTitle(text: '$yAxisName$unit')),
// 启用图例
legend: Legend(isVisible: true, position: LegendPosition.top),
// 启用交互提示(点击数据点显示详情)
tooltipBehavior: TooltipBehavior(
enable: true,
format: 'point.x: point.y $unit',
),
// 折线图数据系列
series: [
LineSeries<ChartData, String>(
dataSource: data,
// X轴数据映射
xValueMapper: (ChartData chart, _) => chart.name,
// Y轴数据映射
yValueMapper: (ChartData chart, _) => chart.value,
// 线条颜色
color: Theme.of(context).colorScheme.primary,
// 线条宽度
width: 3,
// 数据点样式
markerSettings: const MarkerSettings(
isVisible: true,
color: Colors.white,
shape: DataMarkerType.circle,
height: 6,
width: 6,
),
// 折线名称(会显示在图例中)
name: yAxisName,
// 启用数据标签(直接显示数值)
dataLabelSettings: const DataLabelSettings(
isVisible: true,
color: Colors.white,
opacity: 0,
),
// 动画效果
animationDuration: 2000, // 动画时长(毫秒)
),
],
);
}
Widget barChart({
required BuildContext context,
required String xAxisName,
required String yAxisName,
required String unit,
required List<ChartData> data,
}) {
return SfCartesianChart(
// X轴配置类别轴
primaryXAxis: CategoryAxis(majorGridLines: MajorGridLines(width: 0)),
// Y轴配置数值轴
// primaryYAxis: NumericAxis(title: AxisTitle(text: '$yAxisName$unit')),
// 启用交互提示
tooltipBehavior: TooltipBehavior(
enable: true,
format: 'point.x: point.y $unit',
),
// 柱状图数据系列
series: [
ColumnSeries<ChartData, String>(
dataSource: data,
// X轴数据映射
xValueMapper: (ChartData chart, _) => chart.name,
// Y轴数据映射
yValueMapper: (ChartData chart, _) => chart.value,
// 名称
name: yAxisName,
// 柱子颜色
color: Theme.of(context).colorScheme.primary,
// 柱子宽度0-1之间1表示占满类别间隔
width: 0.6,
// 柱子边框
borderWidth: 1,
borderColor: Colors.black12,
// 数据标签
dataLabelSettings: const DataLabelSettings(
isVisible: true,
color: Colors.white,
opacity: 0,
alignment: ChartAlignment.center,
),
// 动画效果
animationDuration: 2000,
),
],
);
}
Widget doubleBarChart({
required BuildContext context,
required String xAxisName,
required String yAxisName,
required String unit,
required List<ChartData> data1,
required List<ChartData> data2,
required String series1Name,
required String series2Name,
}) {
return SfCartesianChart(
// X轴配置类别轴
primaryXAxis: CategoryAxis(majorGridLines: const MajorGridLines(width: 0)),
// Y轴配置数值轴
// primaryYAxis: NumericAxis(title: AxisTitle(text: '$yAxisName$unit')),
// 图例配置
legend: Legend(
isVisible: true,
position: LegendPosition.top,
overflowMode: LegendItemOverflowMode.wrap,
),
// 启用交互提示
tooltipBehavior: TooltipBehavior(
enable: true,
format: 'series.name: point.y $unit',
),
// 双柱状图数据系列
series: <ColumnSeries<ChartData, String>>[
ColumnSeries<ChartData, String>(
dataSource: data1,
// X轴数据映射
xValueMapper: (ChartData chart, _) => chart.name,
// Y轴数据映射
yValueMapper: (ChartData chart, _) => chart.value,
// 系列名称
name: series1Name,
// 柱子颜色
color: Theme.of(context).colorScheme.primary,
// 柱子宽度
width: 0.3,
// 柱子边框
borderWidth: 1,
borderColor: Colors.black12,
// 数据标签
dataLabelSettings: const DataLabelSettings(
isVisible: true,
color: Colors.white,
opacity: 0,
alignment: ChartAlignment.center,
),
// 动画效果
animationDuration: 2000,
),
ColumnSeries<ChartData, String>(
dataSource: data2,
// X轴数据映射
xValueMapper: (ChartData chart, _) => chart.name,
// Y轴数据映射
yValueMapper: (ChartData chart, _) => chart.value,
// 系列名称
name: series2Name,
// 柱子颜色
color: Theme.of(context).colorScheme.inversePrimary,
// 柱子宽度
width: 0.3,
// 柱子边框
borderWidth: 1,
borderColor: Colors.black12,
// 数据标签
dataLabelSettings: const DataLabelSettings(
isVisible: true,
color: Colors.white,
opacity: 0,
alignment: ChartAlignment.center,
),
// 动画效果
animationDuration: 2000,
),
],
);
}
Widget pieChart({
required BuildContext context,
required String unit,
required List<ChartData> data,
}) {
// 计算 value 的总和
double sumValue = data.fold(0.0, (sum, item) => sum + item.value);
return SfCircularChart(
// 饼图标题
// title: ChartTitle(text: '菜谱类别占比分布'),
// 启用图例
legend: const Legend(isVisible: true, position: LegendPosition.right),
// 启用交互提示(点击扇区显示详情)
tooltipBehavior: TooltipBehavior(
enable: true,
format: 'point.x: point.y $unit',
),
// 饼图系列配置
series: [
PieSeries<ChartData, String>(
dataSource: data,
// 类别映射(饼图扇区名称)
xValueMapper: (ChartData data, _) => data.name,
// 数值映射(扇区大小占比)
yValueMapper: (ChartData data, _) => data.value,
// 扇区半径0-1之间1表示充满容器
// radius: '50%',
// 启用扇区分离效果
explode: true,
// 指定分离的扇区索引(这里分离第一个扇区)
explodeIndex: 0,
// 分离距离
explodeOffset: '5%',
dataLabelMapper: (ChartData data, _) {
final percentage = (data.value / sumValue * 100).toStringAsFixed(0);
return '$percentage%';
},
// 数据标签(显示在扇区上的文本)
dataLabelSettings: DataLabelSettings(isVisible: true),
// 动画效果
animationDuration: 2000,
),
],
);
}

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_common/widget/dialog_widget.dart';
import 'package:toggle_switch/toggle_switch.dart'; import 'package:toggle_switch/toggle_switch.dart';
OutlineInputBorder buildFormBoard() { OutlineInputBorder buildFormBoard() {
@@ -22,28 +23,6 @@ OutlineInputBorder buildFormFocusedBoard(ColorScheme colors) {
); );
} }
// 卡片
class BuildCard extends StatelessWidget {
final Widget? child;
const BuildCard({super.key, this.child});
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).colorScheme;
return Container(
decoration: BoxDecoration(
color: colors.surfaceContainer,
borderRadius: BorderRadius.circular(12),
// border: Border.all(color: colors.outline.withAlpha(50), width: 1),
),
padding: const EdgeInsets.all(16),
child: child,
);
}
}
// 底部弹出 // 底部弹出
void buildModalBottom({ void buildModalBottom({
required BuildContext context, required BuildContext context,
@@ -143,7 +122,7 @@ Widget buildDismissible({
background: _buildDismissBackground(), background: _buildDismissBackground(),
confirmDismiss: (direction) async { confirmDismiss: (direction) async {
// 这里实现二次确认 // 这里实现二次确认
return await showDeleteConfirmationDialog(context); return await showConfirmDialog(context, '确定要删除这个项目吗?');
}, },
onDismissed: (direction) { onDismissed: (direction) {
onDelete(); onDelete();
@@ -152,30 +131,6 @@ Widget buildDismissible({
); );
} }
// 确认对话框
Future<bool?> showDeleteConfirmationDialog(BuildContext context) async {
return showDialog<bool>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('确认删除'),
backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
content: const Text('确定要删除这个项目吗?'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('取消'),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text('删除', style: TextStyle(color: Colors.red)),
),
],
);
},
);
}
Widget _buildDismissBackground() { Widget _buildDismissBackground() {
return Container( return Container(
color: Colors.red, color: Colors.red,
@@ -193,16 +148,18 @@ Widget buildToggleSwitch<T extends Enum>({
required List<String> labels, required List<String> labels,
required ValueChanged<T> onTabChanged, required ValueChanged<T> onTabChanged,
}) { }) {
final colors = Theme.of(context).colorScheme;
return ToggleSwitch( return ToggleSwitch(
minWidth: 90.0, minWidth: 90.0,
minHeight: 40.0, minHeight: 40.0,
initialLabelIndex: tabValues.indexOf(currentTab), initialLabelIndex: tabValues.indexOf(currentTab),
totalSwitches: tabValues.length, totalSwitches: tabValues.length,
labels: labels, labels: labels,
activeBgColor: [Theme.of(context).colorScheme.primary], activeBgColor: [colors.primary],
activeFgColor: Colors.white, activeFgColor: Colors.white,
inactiveBgColor: Colors.grey.shade200, inactiveBgColor: colors.surfaceContainer,
inactiveFgColor: Colors.grey.shade700, inactiveFgColor: colors.onSurface,
cornerRadius: 12.0, cornerRadius: 12.0,
customTextStyles: [TextStyle(fontSize: 12, fontWeight: FontWeight.w500)], customTextStyles: [TextStyle(fontSize: 12, fontWeight: FontWeight.w500)],
onToggle: (index) { onToggle: (index) {
@@ -213,6 +170,41 @@ Widget buildToggleSwitch<T extends Enum>({
); );
} }
ButtonStyle buildSegmentStyle(ColorScheme colors) {
return ButtonStyle(
side: WidgetStateProperty.all<BorderSide>(
BorderSide(color: colors.surface, width: 0),
),
iconColor: WidgetStateProperty.resolveWith<Color>((
Set<WidgetState> states,
) {
if (states.contains(WidgetState.selected)) {
return colors.onPrimary;
}
return Colors.grey;
}),
backgroundColor: WidgetStateProperty.resolveWith<Color>((
Set<WidgetState> states,
) {
if (states.contains(WidgetState.selected)) {
return colors.primary;
}
return colors.surfaceContainer;
}),
foregroundColor: WidgetStateProperty.resolveWith<Color>((
Set<WidgetState> states,
) {
if (states.contains(WidgetState.selected)) {
return colors.onPrimary;
}
return colors.onSurface;
}),
shape: WidgetStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
);
}
Widget circleIconButton({ Widget circleIconButton({
required IconData icon, required IconData icon,
required VoidCallback onPressed, required VoidCallback onPressed,

View File

@@ -1,9 +1,9 @@
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:flisp_app/models/flisp.dart'; import 'package:flisp_app/models/flisp.dart';
import 'package:flisp_app/provider/flisp_provider.dart'; import 'package:flisp_app/provider/flisp_provider.dart';
import 'package:flisp_app/utils/minio_utils.dart';
import 'package:flisp_app/widgets/flisp_widget.dart'; import 'package:flisp_app/widgets/flisp_widget.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_common/utils/minio_utils.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -27,6 +27,8 @@ class FlispForm extends StatefulWidget {
class _FlispFormState extends State<FlispForm> { class _FlispFormState extends State<FlispForm> {
bool _showTagOptions = false; bool _showTagOptions = false;
final int maxContentCount = 100;
final String bucketName = 'flisp';
@override @override
void initState() { void initState() {
@@ -51,7 +53,7 @@ class _FlispFormState extends State<FlispForm> {
final provider = Provider.of<FlispProvider>(context, listen: false); final provider = Provider.of<FlispProvider>(context, listen: false);
setState(() { setState(() {
provider.formItem.imageUrl = provider.formItem.imageUrl =
'${MinIOHelper().rustfsFileUrl}/${MinIOHelper().bucketName}/$fileName'; '${MinIOHelper().fileUrl}/$bucketName/$fileName';
}); });
} }
@@ -62,13 +64,9 @@ class _FlispFormState extends State<FlispForm> {
}); });
} }
@override Widget buildContentField(FlispProvider provider) {
Widget build(BuildContext context) {
final int maxContentCount = 100;
final provider = Provider.of<FlispProvider>(context);
final colors = Theme.of(context).colorScheme; final colors = Theme.of(context).colorScheme;
Widget buildContentField() {
return FormBuilderTextField( return FormBuilderTextField(
name: 'content', name: 'content',
initialValue: provider.formItem.content, initialValue: provider.formItem.content,
@@ -80,8 +78,6 @@ class _FlispFormState extends State<FlispForm> {
decoration: InputDecoration( decoration: InputDecoration(
hintText: '记录你的灵感瞬间...', hintText: '记录你的灵感瞬间...',
hintStyle: TextStyle(color: Colors.grey), hintStyle: TextStyle(color: Colors.grey),
counterText: '',
suffixText: '${provider.formItem.content.length}/$maxContentCount',
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.grey.shade200), borderSide: BorderSide(color: Colors.grey.shade200),
@@ -101,6 +97,22 @@ class _FlispFormState extends State<FlispForm> {
minLines: 4, minLines: 4,
maxLines: 8, maxLines: 8,
maxLength: maxContentCount, maxLength: maxContentCount,
buildCounter: (
context, {
required currentLength,
required isFocused,
maxLength,
}) {
return Text(
'$currentLength/$maxContentCount',
style: TextStyle(
color:
currentLength > maxLength!
? Colors.red
: Theme.of(context).colorScheme.primary,
),
);
},
validator: (value) { validator: (value) {
if (value != null && value.isEmpty) { if (value != null && value.isEmpty) {
return '请输入内容'; return '请输入内容';
@@ -114,15 +126,15 @@ class _FlispFormState extends State<FlispForm> {
); );
} }
Widget buildFormBody() { Widget buildFormBody(FlispProvider provider) {
return Expanded( return Expanded(
child: SingleChildScrollView( child: SingleChildScrollView(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
buildContentField(), buildContentField(provider),
SizedBox(height: 8), SizedBox(height: 8),
buildFlispTag(provider.formItem), buildFlispTag(context, provider.formItem),
SizedBox(height: 12), SizedBox(height: 12),
if (provider.formItem.imageUrl.isNotEmpty) if (provider.formItem.imageUrl.isNotEmpty)
buildFlispImage( buildFlispImage(
@@ -145,7 +157,10 @@ class _FlispFormState extends State<FlispForm> {
if (result != null) { if (result != null) {
PlatformFile file = result.files.first; PlatformFile file = result.files.first;
final fileName = await MinIOHelper().uploadFile(file: file); final fileName = await MinIOHelper().uploadFile(
bucketName: bucketName,
file: file,
);
_selectImage(fileName); _selectImage(fileName);
} }
} }
@@ -161,11 +176,7 @@ class _FlispFormState extends State<FlispForm> {
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.blue.withAlpha(80), width: 1), border: Border.all(color: Colors.blue.withAlpha(80), width: 1),
), ),
child: Icon( child: Icon(Icons.photo_library_rounded, size: 20, color: Colors.blue),
Icons.photo_library_rounded,
size: 20,
color: Colors.blue,
),
), ),
); );
} }
@@ -181,11 +192,7 @@ class _FlispFormState extends State<FlispForm> {
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.orange.withAlpha(80), width: 1), border: Border.all(color: Colors.orange.withAlpha(80), width: 1),
), ),
child: Icon( child: Icon(Icons.local_offer_rounded, size: 20, color: Colors.orange),
Icons.local_offer_rounded,
size: 20,
color: Colors.orange,
),
), ),
); );
} }
@@ -195,7 +202,7 @@ class _FlispFormState extends State<FlispForm> {
width: 48, width: 48,
height: 48, height: 48,
decoration: BoxDecoration( decoration: BoxDecoration(
color: colors.primary, color: Theme.of(context).colorScheme.primary,
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(24),
), ),
child: IconButton( child: IconButton(
@@ -215,7 +222,7 @@ class _FlispFormState extends State<FlispForm> {
width: 90, width: 90,
padding: EdgeInsets.all(8), padding: EdgeInsets.all(8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
@@ -243,11 +250,7 @@ class _FlispFormState extends State<FlispForm> {
color: tag.color, color: tag.color,
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: Icon( child: Icon(tag.icon, size: 12, color: Colors.white),
tag.icon,
size: 12,
color: Colors.white,
),
), ),
SizedBox(width: 8), SizedBox(width: 8),
Text(tag.label, style: TextStyle(fontSize: 12)), Text(tag.label, style: TextStyle(fontSize: 12)),
@@ -280,6 +283,10 @@ class _FlispFormState extends State<FlispForm> {
); );
} }
@override
Widget build(BuildContext context) {
final provider = Provider.of<FlispProvider>(context);
// 整体使用Stack布局分离事件层级 // 整体使用Stack布局分离事件层级
return Stack( return Stack(
children: [ children: [
@@ -300,7 +307,9 @@ class _FlispFormState extends State<FlispForm> {
// 主表单内容 // 主表单内容
FormBuilder( FormBuilder(
key: widget.formKey, key: widget.formKey,
child: Column(children: [buildFormBody(), buildBottomButton()]), child: Column(
children: [buildFormBody(provider), buildBottomButton()],
),
), ),
// 标签弹窗(放在最上层,确保事件优先响应) // 标签弹窗(放在最上层,确保事件优先响应)

View File

@@ -1,30 +1,31 @@
import 'package:flisp_app/models/flisp.dart'; import 'package:flisp_app/models/flisp.dart';
import 'package:flisp_app/widgets/common.dart'; import 'package:flisp_app/widgets/common.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_common/widget/common_widget.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:toggle_switch/toggle_switch.dart';
Widget buildFlispStatsCard(List<Flisp> flisps) { Widget buildFlispStatsCard(BuildContext context, List<Flisp> flisps) {
int totalCount = flisps.length; int totalCount = flisps.length;
int lifeCount = flisps.where((flisp) => flisp.tag == FlispTag.life).length; int lifeCount = flisps.where((flisp) => flisp.tag == FlispTag.life).length;
int workCount = flisps.where((flisp) => flisp.tag == FlispTag.work).length; int workCount = flisps.where((flisp) => flisp.tag == FlispTag.work).length;
int studyCount = flisps.where((flisp) => flisp.tag == FlispTag.study).length; int studyCount = flisps.where((flisp) => flisp.tag == FlispTag.study).length;
return BuildCard( return CommonCard(
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
_buildStatItem('总计', totalCount, Colors.orange), _buildStatItem(context, '总计', totalCount),
_buildStatItem('生活', lifeCount, FlispTag.life.color), _buildStatItem(context, '生活', lifeCount),
_buildStatItem('工作', workCount, FlispTag.work.color), _buildStatItem(context, '工作', workCount),
_buildStatItem('学习', studyCount, FlispTag.study.color), _buildStatItem(context, '学习', studyCount),
], ],
), ),
); );
} }
Widget _buildStatItem(String label, int count, Color color) { Widget _buildStatItem(BuildContext context, String label, int count) {
return Column( return Column(
children: [ children: [
Text( Text(
@@ -32,7 +33,7 @@ Widget _buildStatItem(String label, int count, Color color) {
style: TextStyle( style: TextStyle(
fontSize: 24, fontSize: 24,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: color, color: Theme.of(context).colorScheme.primary,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
@@ -79,12 +80,14 @@ Widget buildFlispList({
} }
Widget _buildFlispContent(BuildContext context, Flisp flisp) { Widget _buildFlispContent(BuildContext context, Flisp flisp) {
final colors = Theme.of(context).colorScheme;
return RichText( return RichText(
text: TextSpan( text: TextSpan(
text: flisp.content, text: flisp.content,
style: TextStyle( style: TextStyle(
fontFamily: 'CustomFont', fontFamily: 'CustomFont',
color: Theme.of(context).colorScheme.onSurface, color: colors.onSurface,
fontSize: 16, fontSize: 16,
), ),
), ),
@@ -207,13 +210,15 @@ Widget _buildFlispVideo(Flisp flisp) {
); );
} }
Widget buildFlispTag(Flisp flisp) { Widget buildFlispTag(BuildContext context, Flisp flisp) {
final colors = Theme.of(context).colorScheme;
return Container( return Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6), padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration( decoration: BoxDecoration(
color: flisp.tag.color.withAlpha(30), color: colors.secondaryContainer,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
border: Border.all(color: flisp.tag.color.withAlpha(60)), // border: Border.all(color: flisp.tag.color.withAlpha(60)),
), ),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@@ -222,7 +227,7 @@ Widget buildFlispTag(Flisp flisp) {
width: 14, width: 14,
height: 14, height: 14,
decoration: BoxDecoration( decoration: BoxDecoration(
color: flisp.tag.color, color: colors.secondary,
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: Icon(flisp.tag.icon, size: 8, color: Colors.white), child: Icon(flisp.tag.icon, size: 8, color: Colors.white),
@@ -232,7 +237,7 @@ Widget buildFlispTag(Flisp flisp) {
flisp.tag.label, flisp.tag.label,
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: flisp.tag.color, color: colors.secondary,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
@@ -248,9 +253,13 @@ Widget buildFlispTime(Flisp flisp) {
); );
} }
Widget _buildFlispSuffix(Flisp flisp) { Widget _buildFlispSuffix(BuildContext context, Flisp flisp) {
return Row( return Row(
children: [buildFlispTag(flisp), const Spacer(), buildFlispTime(flisp)], children: [
buildFlispTag(context, flisp),
const Spacer(),
buildFlispTime(flisp),
],
); );
} }
@@ -267,7 +276,7 @@ Widget _buildFlispItem({
context: context, context: context,
id: flisp.id, id: flisp.id,
onDelete: () => onDelete(flisp), onDelete: () => onDelete(flisp),
child: BuildCard( child: CommonCard(
child: InkWell( child: InkWell(
onTap: () => onEdit(flisp), onTap: () => onEdit(flisp),
child: Column( child: Column(
@@ -279,7 +288,7 @@ Widget _buildFlispItem({
const SizedBox(height: 4), const SizedBox(height: 4),
if (hasVideo) _buildFlispVideo(flisp), if (hasVideo) _buildFlispVideo(flisp),
const SizedBox(height: 4), const SizedBox(height: 4),
_buildFlispSuffix(flisp), _buildFlispSuffix(context, flisp),
], ],
), ),
), ),

View File

@@ -1,5 +1,5 @@
import 'package:flisp_app/widgets/common.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_common/widget/common_widget.dart';
class StatisticCard extends StatelessWidget { class StatisticCard extends StatelessWidget {
final IconData icon; final IconData icon;
@@ -19,7 +19,7 @@ class StatisticCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BuildCard( return CommonCard(
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [

View File

@@ -22,25 +22,21 @@ class TodoForm extends StatefulWidget {
} }
class _TodoFormState extends State<TodoForm> { class _TodoFormState extends State<TodoForm> {
final int maxTitleCount = 10;
final int maxContentCount = 20;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
} }
@override
Widget build(BuildContext context) {
final int maxTitleCount = 10;
final int maxContentCount = 20;
final provider = Provider.of<TodoProvider>(context);
final colors = Theme.of(context).colorScheme;
Widget buildTitle() { Widget buildTitle() {
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon( Icon(
widget.isEditing ? Icons.edit_note : Icons.add_task, widget.isEditing ? Icons.edit_note : Icons.add_task,
color: colors.primary, color: Theme.of(context).colorScheme.primary,
size: 24, size: 24,
), ),
SizedBox(width: 8), SizedBox(width: 8),
@@ -49,14 +45,16 @@ class _TodoFormState extends State<TodoForm> {
style: TextStyle( style: TextStyle(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: colors.primary, color: Theme.of(context).colorScheme.primary,
), ),
), ),
], ],
); );
} }
FormBuilderTextField buildTitleField() { FormBuilderTextField buildTitleField(TodoProvider provider) {
final colors = Theme.of(context).colorScheme;
return FormBuilderTextField( return FormBuilderTextField(
name: 'title', name: 'title',
initialValue: provider.formItem.title, initialValue: provider.formItem.title,
@@ -69,7 +67,11 @@ class _TodoFormState extends State<TodoForm> {
label: RichText( label: RichText(
text: TextSpan( text: TextSpan(
text: '标题', text: '标题',
style: TextStyle(color: Colors.grey.shade700, fontSize: 16), style: TextStyle(
color: Colors.grey,
fontSize: 16,
fontFamily: 'CustomFont',
),
children: const [ children: const [
TextSpan( TextSpan(
text: '*', text: '*',
@@ -116,7 +118,9 @@ class _TodoFormState extends State<TodoForm> {
); );
} }
FormBuilderTextField buildContentField() { FormBuilderTextField buildContentField(TodoProvider provider) {
final colors = Theme.of(context).colorScheme;
return FormBuilderTextField( return FormBuilderTextField(
name: 'content', name: 'content',
initialValue: provider.formItem.content, initialValue: provider.formItem.content,
@@ -127,6 +131,7 @@ class _TodoFormState extends State<TodoForm> {
}, },
decoration: InputDecoration( decoration: InputDecoration(
labelText: '内容', labelText: '内容',
labelStyle: TextStyle(color: Colors.grey),
hintText: '请输入待办事项内容...', hintText: '请输入待办事项内容...',
hintStyle: TextStyle(color: Colors.grey), hintStyle: TextStyle(color: Colors.grey),
counterText: '', counterText: '',
@@ -159,7 +164,7 @@ class _TodoFormState extends State<TodoForm> {
); );
} }
IconButton buildClearDueDateSuffixIcon() { IconButton buildClearDueDateSuffixIcon(TodoProvider provider) {
return IconButton( return IconButton(
icon: Icon(Icons.clear, size: 18), icon: Icon(Icons.clear, size: 18),
onPressed: () { onPressed: () {
@@ -171,7 +176,9 @@ class _TodoFormState extends State<TodoForm> {
); );
} }
FormBuilderDateTimePicker buildDueDateField() { FormBuilderDateTimePicker buildDueDateField(TodoProvider provider) {
final colors = Theme.of(context).colorScheme;
return FormBuilderDateTimePicker( return FormBuilderDateTimePicker(
name: 'dueDate', name: 'dueDate',
format: DateFormat('yyyy-MM-dd'), format: DateFormat('yyyy-MM-dd'),
@@ -183,16 +190,8 @@ class _TodoFormState extends State<TodoForm> {
}); });
}, },
decoration: InputDecoration( decoration: InputDecoration(
labelText: labelText: provider.formItem.dueDate == null ? '选择截止日期' : '截止日期: ',
provider.formItem.dueDate == null labelStyle: TextStyle(color: Colors.grey),
? '选择截止日期'
: '截止: ${DateFormat('yyyy-MM-dd').format(provider.formItem.dueDate!)}',
labelStyle: TextStyle(
color:
provider.formItem.dueDate == null
? Colors.grey
: Colors.black87,
),
prefixIcon: Icon(Icons.calendar_month, color: Colors.purple), prefixIcon: Icon(Icons.calendar_month, color: Colors.purple),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
@@ -211,7 +210,7 @@ class _TodoFormState extends State<TodoForm> {
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 14), contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 14),
suffixIcon: suffixIcon:
provider.formItem.dueDate != null provider.formItem.dueDate != null
? buildClearDueDateSuffixIcon() ? buildClearDueDateSuffixIcon(provider)
: null, : null,
), ),
validator: (value) { validator: (value) {
@@ -224,7 +223,7 @@ class _TodoFormState extends State<TodoForm> {
); );
} }
IconButton buildClearScheduledTimeSuffixIcon() { IconButton buildClearScheduledTimeSuffixIcon(TodoProvider provider) {
return IconButton( return IconButton(
icon: Icon(Icons.clear, size: 18), icon: Icon(Icons.clear, size: 18),
onPressed: () { onPressed: () {
@@ -236,7 +235,9 @@ class _TodoFormState extends State<TodoForm> {
); );
} }
FormBuilderDateTimePicker buildScheduledTimeField() { FormBuilderDateTimePicker buildScheduledTimeField(TodoProvider provider) {
final colors = Theme.of(context).colorScheme;
return FormBuilderDateTimePicker( return FormBuilderDateTimePicker(
name: 'scheduledTime', name: 'scheduledTime',
format: DateFormat('yyyy-MM-dd HH:mm:ss'), format: DateFormat('yyyy-MM-dd HH:mm:ss'),
@@ -249,15 +250,8 @@ class _TodoFormState extends State<TodoForm> {
}, },
decoration: InputDecoration( decoration: InputDecoration(
labelText: labelText:
provider.formItem.scheduledTime == null provider.formItem.scheduledTime == null ? '选择提醒时间' : '提醒时间: ',
? '选择提醒日期' labelStyle: TextStyle(color: Colors.grey),
: '提醒: ${DateFormat('yyyy-MM-dd HH:mm:ss').format(provider.formItem.scheduledTime!)}',
labelStyle: TextStyle(
color:
provider.formItem.scheduledTime == null
? Colors.grey
: Colors.black87,
),
prefixIcon: Icon(Icons.calendar_month, color: Colors.purple), prefixIcon: Icon(Icons.calendar_month, color: Colors.purple),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
@@ -276,7 +270,7 @@ class _TodoFormState extends State<TodoForm> {
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 14), contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 14),
suffixIcon: suffixIcon:
provider.formItem.scheduledTime != null provider.formItem.scheduledTime != null
? buildClearScheduledTimeSuffixIcon() ? buildClearScheduledTimeSuffixIcon(provider)
: null, : null,
), ),
validator: (value) { validator: (value) {
@@ -288,7 +282,7 @@ class _TodoFormState extends State<TodoForm> {
); );
} }
FormBuilderRadioGroup builderRadioGroup() { FormBuilderRadioGroup builderRadioGroup(TodoProvider provider) {
return FormBuilderRadioGroup<TodoPriority>( return FormBuilderRadioGroup<TodoPriority>(
name: 'priority', name: 'priority',
initialValue: provider.formItem.priority, initialValue: provider.formItem.priority,
@@ -318,10 +312,10 @@ class _TodoFormState extends State<TodoForm> {
); );
} }
Container buildPriorityField() { Container buildPriorityField(TodoProvider provider) {
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: colors.surface, color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
padding: EdgeInsets.all(12), padding: EdgeInsets.all(12),
@@ -335,37 +329,41 @@ class _TodoFormState extends State<TodoForm> {
Text('优先级'), Text('优先级'),
], ],
), ),
builderRadioGroup(), builderRadioGroup(provider),
], ],
), ),
); );
} }
FormBuilder buildForm() { FormBuilder buildForm(TodoProvider provider) {
return FormBuilder( return FormBuilder(
key: widget.formKey, key: widget.formKey,
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
buildTitleField(), buildTitleField(provider),
SizedBox(height: 12), SizedBox(height: 12),
buildContentField(), buildContentField(provider),
SizedBox(height: 12), SizedBox(height: 12),
buildDueDateField(), buildDueDateField(provider),
SizedBox(height: 12), SizedBox(height: 12),
buildScheduledTimeField(), buildScheduledTimeField(provider),
SizedBox(height: 12), SizedBox(height: 12),
buildPriorityField(), buildPriorityField(provider),
], ],
), ),
); );
} }
@override
Widget build(BuildContext context) {
final provider = Provider.of<TodoProvider>(context);
return Padding( return Padding(
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 10), padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 10),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [buildTitle(), SizedBox(height: 20), buildForm()], children: [buildTitle(), SizedBox(height: 20), buildForm(provider)],
), ),
); );
} }

View File

@@ -2,29 +2,31 @@ import 'package:flisp_app/models/todo.dart';
import 'package:flisp_app/utils/todo_utils.dart'; import 'package:flisp_app/utils/todo_utils.dart';
import 'package:flisp_app/widgets/common.dart'; import 'package:flisp_app/widgets/common.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_common/widget/common_widget.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
// 统计卡片 // 统计卡片
Widget buildTodoStatsCard(List<Todo> todos) { Widget buildTodoStatsCard(BuildContext context, List<Todo> todos) {
int totalCount = todos.length; int totalCount = todos.length;
int activeCount = todos.where((todo) => !todo.isCompleted).length; int activeCount = todos.where((todo) => !todo.isCompleted).length;
int completedCount = todos.where((todo) => todo.isCompleted).length; int completedCount = todos.where((todo) => todo.isCompleted).length;
return BuildCard( return CommonCard(
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
_buildStatItem('总计', totalCount, Colors.blue), _buildStatItem(context, '总计', totalCount),
_buildStatItem('待完成', activeCount, Colors.orange), _buildStatItem(context, '待完成', activeCount),
_buildStatItem('已完成', completedCount, Colors.green), _buildStatItem(context, '已完成', completedCount),
], ],
), ),
); );
} }
Widget _buildStatItem(String label, int count, Color color) { Widget _buildStatItem(BuildContext context, String label, int count) {
return Column( return Column(
children: [ children: [
Text( Text(
@@ -32,7 +34,7 @@ Widget _buildStatItem(String label, int count, Color color) {
style: TextStyle( style: TextStyle(
fontSize: 24, fontSize: 24,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: color, color: Theme.of(context).colorScheme.primary,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
@@ -93,7 +95,7 @@ Widget _buildTodoItem({
context: context, context: context,
id: todo.id, id: todo.id,
onDelete: () => onDelete(todo), onDelete: () => onDelete(todo),
child: BuildCard( child: CommonCard(
child: ListTile( child: ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 8, vertical: 0), contentPadding: EdgeInsets.symmetric(horizontal: 8, vertical: 0),
leading: SizedBox( leading: SizedBox(

View File

@@ -1,119 +0,0 @@
import 'package:flisp_app/widgets/common.dart';
import 'package:flutter/material.dart';
class YearSelector extends StatefulWidget {
final int initialYear;
final int? minYear;
final int? maxYear;
final Function(int) onYearChanged;
const YearSelector({
super.key,
required this.initialYear,
required this.onYearChanged,
this.minYear,
this.maxYear,
});
@override
State<YearSelector> createState() => _YearSelectorState();
}
// SingleTickerProviderStateMixin 动画控制器
class _YearSelectorState extends State<YearSelector>
with SingleTickerProviderStateMixin {
late int _currentYear;
// 用于动画效果
late AnimationController _animationController;
late Animation<double> _scaleAnimation;
@override
void initState() {
super.initState();
_currentYear = widget.initialYear;
// 初始化动画控制器
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 200),
);
// 缩放动画
_scaleAnimation = Tween<double>(begin: 1.0, end: 1.1).animate(
CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
/// 切换到上一年
void _previousYear() {
if (widget.minYear == null || _currentYear > widget.minYear!) {
_animateYearChange(() {
setState(() {
_currentYear--;
});
widget.onYearChanged(_currentYear);
});
}
}
/// 切换到下一年
void _nextYear() {
if (widget.maxYear == null || _currentYear < widget.maxYear!) {
_animateYearChange(() {
setState(() {
_currentYear++;
});
widget.onYearChanged(_currentYear);
});
}
}
/// 年份变化时的动画效果
void _animateYearChange(VoidCallback onComplete) {
_animationController.forward().then((_) {
onComplete();
_animationController.reverse();
});
}
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
circleIconButton(
context: context,
icon: Icons.chevron_left,
onPressed: () => _previousYear(),
),
// 年份显示
AnimatedBuilder(
animation: _scaleAnimation,
builder: (context, child) {
return Transform.scale(scale: _scaleAnimation.value, child: child);
},
child: Text(
'$_currentYear',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Theme.of(context).primaryColor,
),
),
),
circleIconButton(
context: context,
icon: Icons.chevron_right,
onPressed: () => _nextYear(),
)
],
);
}
}

View File

@@ -6,6 +6,7 @@ import FlutterMacOS
import Foundation import Foundation
import file_picker import file_picker
import flutter_image_compress_macos
import flutter_local_notifications import flutter_local_notifications
import package_info_plus import package_info_plus
import path_provider_foundation import path_provider_foundation
@@ -14,6 +15,7 @@ import shared_preferences_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
FlutterImageCompressMacosPlugin.register(with: registry.registrar(forPlugin: "FlutterImageCompressMacosPlugin"))
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))

View File

@@ -39,7 +39,7 @@ packages:
source: hosted source: hosted
version: "2.12.0" version: "2.12.0"
awesome_dialog: awesome_dialog:
dependency: "direct main" dependency: transitive
description: description:
name: awesome_dialog name: awesome_dialog
sha256: "4c5821a0a637ceee022084e78c1b8237dd4b8bfca4dd24ac2484662a56707338" sha256: "4c5821a0a637ceee022084e78c1b8237dd4b8bfca4dd24ac2484662a56707338"
@@ -183,7 +183,7 @@ packages:
source: hosted source: hosted
version: "0.3.5" version: "0.3.5"
crypto: crypto:
dependency: "direct main" dependency: transitive
description: description:
name: crypto name: crypto
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
@@ -214,6 +214,22 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "0.7.11" version: "0.7.11"
dio:
dependency: transitive
description:
name: dio
sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9
url: "https://pub.flutter-io.cn"
source: hosted
version: "5.9.0"
dio_web_adapter:
dependency: transitive
description:
name: dio_web_adapter
sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.1"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@@ -259,6 +275,13 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_common:
dependency: "direct main"
description:
path: "../flutter_common"
relative: true
source: path
version: "1.0.0+1"
flutter_form_builder: flutter_form_builder:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -267,6 +290,54 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "10.0.1" version: "10.0.1"
flutter_image_compress:
dependency: transitive
description:
name: flutter_image_compress
sha256: "51d23be39efc2185e72e290042a0da41aed70b14ef97db362a6b5368d0523b27"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.0"
flutter_image_compress_common:
dependency: transitive
description:
name: flutter_image_compress_common
sha256: c5c5d50c15e97dd7dc72ff96bd7077b9f791932f2076c5c5b6c43f2c88607bfb
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.6"
flutter_image_compress_macos:
dependency: transitive
description:
name: flutter_image_compress_macos
sha256: "20019719b71b743aba0ef874ed29c50747461e5e8438980dfa5c2031898f7337"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.3"
flutter_image_compress_ohos:
dependency: transitive
description:
name: flutter_image_compress_ohos
sha256: e76b92bbc830ee08f5b05962fc78a532011fcd2041f620b5400a593e96da3f51
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.0.3"
flutter_image_compress_platform_interface:
dependency: transitive
description:
name: flutter_image_compress_platform_interface
sha256: "579cb3947fd4309103afe6442a01ca01e1e6f93dc53bb4cbd090e8ce34a41889"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.5"
flutter_image_compress_web:
dependency: transitive
description:
name: flutter_image_compress_web
sha256: b9b141ac7c686a2ce7bb9a98176321e1182c9074650e47bb140741a44b6f5a96
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.1.5"
flutter_lints: flutter_lints:
dependency: "direct dev" dependency: "direct dev"
description: description:
@@ -279,26 +350,34 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_local_notifications name: flutter_local_notifications
sha256: ef41ae901e7529e52934feba19ed82827b11baa67336829564aeab3129460610 sha256: "19ffb0a8bb7407875555e5e98d7343a633bb73707bae6c6a5f37c90014077875"
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "18.0.1" version: "19.5.0"
flutter_local_notifications_linux: flutter_local_notifications_linux:
dependency: transitive dependency: transitive
description: description:
name: flutter_local_notifications_linux name: flutter_local_notifications_linux
sha256: "8f685642876742c941b29c32030f6f4f6dacd0e4eaecb3efbb187d6a3812ca01" sha256: e3c277b2daab8e36ac5a6820536668d07e83851aeeb79c446e525a70710770a5
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "5.0.0" version: "6.0.0"
flutter_local_notifications_platform_interface: flutter_local_notifications_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: flutter_local_notifications_platform_interface name: flutter_local_notifications_platform_interface
sha256: "6c5b83c86bf819cdb177a9247a3722067dd8cc6313827ce7c77a4b238a26fd52" sha256: "277d25d960c15674ce78ca97f57d0bae2ee401c844b6ac80fcd972a9c99d09fe"
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "8.0.0" version: "9.1.0"
flutter_local_notifications_windows:
dependency: transitive
description:
name: flutter_local_notifications_windows
sha256: "8d658f0d367c48bd420e7cf2d26655e2d1130147bca1eea917e576ca76668aaf"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.3"
flutter_localizations: flutter_localizations:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@@ -323,7 +402,7 @@ packages:
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
fluttertoast: fluttertoast:
dependency: "direct main" dependency: transitive
description: description:
name: fluttertoast name: fluttertoast
sha256: "90778fe0497fe3a09166e8cf2e0867310ff434b794526589e77ec03cf08ba8e8" sha256: "90778fe0497fe3a09166e8cf2e0867310ff434b794526589e77ec03cf08ba8e8"
@@ -474,6 +553,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "5.1.1" version: "5.1.1"
logger:
dependency: transitive
description:
name: logger
sha256: a7967e31b703831a893bbc3c3dd11db08126fe5f369b5c648a36f821979f5be3
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.6.2"
logging: logging:
dependency: transitive dependency: transitive
description: description:
@@ -523,7 +610,7 @@ packages:
source: hosted source: hosted
version: "2.0.0" version: "2.0.0"
minio: minio:
dependency: "direct main" dependency: transitive
description: description:
name: minio name: minio
sha256: ee2ce47766e46c7d164f960f2f5ed6a9a82844d877f6b82574f6876ec50c56d1 sha256: ee2ce47766e46c7d164f960f2f5ed6a9a82844d877f6b82574f6876ec50c56d1
@@ -691,7 +778,7 @@ packages:
source: hosted source: hosted
version: "0.0.16" version: "0.0.16"
shared_preferences: shared_preferences:
dependency: "direct main" dependency: transitive
description: description:
name: shared_preferences name: shared_preferences
sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
@@ -832,7 +919,7 @@ packages:
source: hosted source: hosted
version: "30.2.7" version: "30.2.7"
syncfusion_flutter_charts: syncfusion_flutter_charts:
dependency: "direct main" dependency: transitive
description: description:
name: syncfusion_flutter_charts name: syncfusion_flutter_charts
sha256: "68fdb029dad34a46e4c9cfad8ad66fe29db7b303bd96849261ab2b23a168d0e8" sha256: "68fdb029dad34a46e4c9cfad8ad66fe29db7b303bd96849261ab2b23a168d0e8"

View File

@@ -1,64 +1 @@
name: flisp_app name: flisp_app
description: "闪灵"
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1
environment:
sdk: ^3.7.0
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8
# 状态管理
provider: ^6.1.1
# 本地存储
shared_preferences: ^2.2.2
# 对话框组件
awesome_dialog: ^3.3.0
# 切换开关组件
toggle_switch: ^2.3.0
# 轻量级提示
fluttertoast: ^8.2.2
# 表单构建器
flutter_form_builder: ^10.0.0
# 表单验证器
form_builder_validators: ^10.0.0
# 国际化支持
intl: ^0.19.0
# 轻量级NoSQL数据库
hive: ^2.2.3
# Hive的Flutter集成
hive_flutter: ^1.1.0
# 路径提供器
path_provider: ^2.1.1
# 本地通知
flutter_local_notifications: ^18.0.0
# 时区支持
timezone: ^0.10.1
file_picker: ^10.3.3
minio: ^3.5.8
crypto: ^3.0.7
syncfusion_flutter_calendar: ^30.1.37
syncfusion_localizations: ^30.1.37
syncfusion_flutter_charts: ^30.1.41
package_info_plus: ^8.0.0
dev_dependencies:
flutter_test:
sdk: flutter
hive_generator: ^2.0.1
build_runner: ^2.4.6
flutter_lints: ^5.0.0
flutter:
uses-material-design: true
fonts:
- family: CustomFont
fonts:
- asset: fonts/custom.ttf

View File

@@ -7,6 +7,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
) )
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST
flutter_local_notifications_windows
) )
set(PLUGIN_BUNDLED_LIBRARIES) set(PLUGIN_BUNDLED_LIBRARIES)