feat: 重构 Reminder Notification 系统并更新应用包名
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:drag_and_drop_lists/drag_and_drop_lists.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:lucide_icons/lucide_icons.dart';
|
||||
import '../../../../app/di/injection.dart';
|
||||
@@ -398,65 +397,6 @@ class _TodoQuadrantsScreenState extends State<TodoQuadrantsScreen> {
|
||||
Widget _buildDragBoard() {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final palette = Theme.of(context).extension<AppColorPalette>()!;
|
||||
final quadrants = [
|
||||
_QuadrantMeta(
|
||||
value: 1,
|
||||
title: context.l10n.todoQuadrantImportantUrgent,
|
||||
textColor: palette.g1Text,
|
||||
dividerColor: palette.g1Divider,
|
||||
borderColor: palette.g1Border,
|
||||
items: _importantUrgent,
|
||||
),
|
||||
_QuadrantMeta(
|
||||
value: 3,
|
||||
title: context.l10n.todoQuadrantUrgentNotImportant,
|
||||
textColor: palette.g3Text,
|
||||
dividerColor: palette.g3Divider,
|
||||
borderColor: palette.g3Border,
|
||||
items: _urgentNotImportant,
|
||||
),
|
||||
_QuadrantMeta(
|
||||
value: 2,
|
||||
title: context.l10n.todoQuadrantImportantNotUrgent,
|
||||
textColor: palette.g2Text,
|
||||
dividerColor: palette.g2Divider,
|
||||
borderColor: palette.g2Border,
|
||||
items: _importantNotUrgent,
|
||||
),
|
||||
];
|
||||
|
||||
final lists = quadrants
|
||||
.map(
|
||||
(meta) => DragAndDropList(
|
||||
canDrag: false,
|
||||
header: _buildQuadrantHeader(meta),
|
||||
contentsWhenEmpty: _buildEmptyQuadrant(),
|
||||
lastTarget: const SizedBox(height: AppSpacing.lg),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(color: meta.borderColor),
|
||||
),
|
||||
children: meta.items
|
||||
.map(
|
||||
(item) => DragAndDropItem(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: AppSpacing.sm,
|
||||
),
|
||||
child: _TodoItemWidget(
|
||||
key: ValueKey(item.id),
|
||||
item: item,
|
||||
onComplete: () => _completeTodo(item),
|
||||
onTap: () => _navigateToDetail(item),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(growable: false),
|
||||
),
|
||||
)
|
||||
.toList(growable: false);
|
||||
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
@@ -465,36 +405,199 @@ class _TodoQuadrantsScreenState extends State<TodoQuadrantsScreen> {
|
||||
AppSpacing.lg,
|
||||
96,
|
||||
),
|
||||
child: DragAndDropLists(
|
||||
children: lists,
|
||||
onItemReorder: _onItemReorder,
|
||||
onListReorder: (oldListIndex, newListIndex) {},
|
||||
listDivider: const SizedBox(height: AppSpacing.md),
|
||||
itemDivider: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
|
||||
child: Container(height: 1, color: colorScheme.surfaceContainerHigh),
|
||||
),
|
||||
listPadding: EdgeInsets.zero,
|
||||
itemDecorationWhileDragging: BoxDecoration(
|
||||
color: colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(AppRadius.md),
|
||||
border: Border.all(color: colorScheme.outlineVariant),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: colorScheme.shadow.withValues(alpha: 0.16),
|
||||
blurRadius: AppRadius.md,
|
||||
offset: const Offset(0, AppSpacing.xs),
|
||||
),
|
||||
],
|
||||
),
|
||||
itemGhost: const SizedBox(height: 42),
|
||||
itemDragOnLongPress: true,
|
||||
lastItemTargetHeight: AppSpacing.xl,
|
||||
disableScrolling: true,
|
||||
child: Column(
|
||||
children: [
|
||||
_buildQuadrant(
|
||||
value: 1,
|
||||
title: context.l10n.todoQuadrantImportantUrgent,
|
||||
textColor: palette.g1Text,
|
||||
dividerColor: palette.g1Divider,
|
||||
borderColor: palette.g1Border,
|
||||
items: _importantUrgent,
|
||||
colorScheme: colorScheme,
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
_buildQuadrant(
|
||||
value: 3,
|
||||
title: context.l10n.todoQuadrantUrgentNotImportant,
|
||||
textColor: palette.g3Text,
|
||||
dividerColor: palette.g3Divider,
|
||||
borderColor: palette.g3Border,
|
||||
items: _urgentNotImportant,
|
||||
colorScheme: colorScheme,
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
_buildQuadrant(
|
||||
value: 2,
|
||||
title: context.l10n.todoQuadrantImportantNotUrgent,
|
||||
textColor: palette.g2Text,
|
||||
dividerColor: palette.g2Divider,
|
||||
borderColor: palette.g2Border,
|
||||
items: _importantNotUrgent,
|
||||
colorScheme: colorScheme,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildQuadrant({
|
||||
required int value,
|
||||
required String title,
|
||||
required Color textColor,
|
||||
required Color dividerColor,
|
||||
required Color borderColor,
|
||||
required List<TodoResponse> items,
|
||||
required ColorScheme colorScheme,
|
||||
}) {
|
||||
return DragTarget<_TodoDragInfo>(
|
||||
onWillAcceptWithDetails: (details) => true,
|
||||
onAcceptWithDetails: (details) {
|
||||
final info = details.data;
|
||||
if (info.sourceQuadrant != value) {
|
||||
_onItemReorder(
|
||||
info.sourceIndex,
|
||||
_listIndexByQuadrant(info.sourceQuadrant),
|
||||
0,
|
||||
_listIndexByQuadrant(value),
|
||||
);
|
||||
}
|
||||
},
|
||||
builder: (context, candidateData, rejectedData) {
|
||||
final isHovering = candidateData.isNotEmpty;
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(
|
||||
color: isHovering ? colorScheme.primary : borderColor,
|
||||
width: isHovering ? 2 : 1,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_buildQuadrantHeader(
|
||||
_QuadrantMeta(
|
||||
value: value,
|
||||
title: title,
|
||||
textColor: textColor,
|
||||
dividerColor: dividerColor,
|
||||
borderColor: borderColor,
|
||||
items: items,
|
||||
),
|
||||
),
|
||||
if (items.isEmpty)
|
||||
_buildEmptyContent(colorScheme)
|
||||
else
|
||||
..._buildItemList(items, value, colorScheme),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDraggableItem(
|
||||
TodoResponse item,
|
||||
int sourceQuadrant,
|
||||
ColorScheme colorScheme,
|
||||
) {
|
||||
final sourceIndex = _sortedQuadrantTodos(
|
||||
sourceQuadrant,
|
||||
).indexWhere((t) => t.id == item.id);
|
||||
final dragInfo = _TodoDragInfo(
|
||||
todoId: item.id,
|
||||
sourceQuadrant: sourceQuadrant,
|
||||
sourceIndex: sourceIndex,
|
||||
);
|
||||
|
||||
return LongPressDraggable<_TodoDragInfo>(
|
||||
data: dragInfo,
|
||||
delay: const Duration(milliseconds: 150),
|
||||
feedback: Material(
|
||||
elevation: 4,
|
||||
borderRadius: BorderRadius.circular(AppRadius.md),
|
||||
child: Container(
|
||||
width: 280,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: AppSpacing.md,
|
||||
vertical: AppSpacing.sm,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(AppRadius.md),
|
||||
),
|
||||
child: Text(
|
||||
item.title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
childWhenDragging: Opacity(
|
||||
opacity: 0.3,
|
||||
child: _TodoItemWidget(
|
||||
key: ValueKey(item.id),
|
||||
item: item,
|
||||
onComplete: () => _completeTodo(item),
|
||||
onTap: () => _navigateToDetail(item),
|
||||
),
|
||||
),
|
||||
child: _TodoItemWidget(
|
||||
key: ValueKey(item.id),
|
||||
item: item,
|
||||
onComplete: () => _completeTodo(item),
|
||||
onTap: () => _navigateToDetail(item),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEmptyContent(ColorScheme colorScheme) {
|
||||
return SizedBox(
|
||||
height: 60,
|
||||
child: Center(
|
||||
child: Text(
|
||||
context.l10n.todoNoItems,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 13,
|
||||
color: colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildItemList(
|
||||
List<TodoResponse> items,
|
||||
int quadrant,
|
||||
ColorScheme colorScheme,
|
||||
) {
|
||||
final result = <Widget>[];
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
result.add(_buildDraggableItem(items[i], quadrant, colorScheme));
|
||||
if (i < items.length - 1) {
|
||||
result.add(
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
|
||||
child: Container(
|
||||
height: 1,
|
||||
color: colorScheme.surfaceContainerHigh,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Widget _buildQuadrantHeader(_QuadrantMeta meta) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@@ -533,21 +636,17 @@ class _TodoQuadrantsScreenState extends State<TodoQuadrantsScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEmptyQuadrant() {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
return SizedBox(
|
||||
height: 60,
|
||||
child: Center(
|
||||
child: Text(
|
||||
context.l10n.todoNoItems,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 13,
|
||||
color: colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
int _listIndexByQuadrant(int quadrant) {
|
||||
switch (quadrant) {
|
||||
case 1:
|
||||
return 0;
|
||||
case 3:
|
||||
return 1;
|
||||
case 2:
|
||||
return 2;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildBottomDock() {
|
||||
@@ -571,6 +670,18 @@ class _TodoQuadrantsScreenState extends State<TodoQuadrantsScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
class _TodoDragInfo {
|
||||
final String todoId;
|
||||
final int sourceQuadrant;
|
||||
final int sourceIndex;
|
||||
|
||||
const _TodoDragInfo({
|
||||
required this.todoId,
|
||||
required this.sourceQuadrant,
|
||||
required this.sourceIndex,
|
||||
});
|
||||
}
|
||||
|
||||
class _ReorderResult {
|
||||
final List<TodoResponse> todos;
|
||||
final List<TodoResponse> changedTodos;
|
||||
|
||||
Reference in New Issue
Block a user