feat: 实现日历提醒 in-app fallback 机制及通知服务重构
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../core/theme/design_tokens.dart';
|
||||
import 'app_button.dart';
|
||||
|
||||
class AppSelectionItem<T> {
|
||||
const AppSelectionItem({required this.value, required this.label});
|
||||
|
||||
final T value;
|
||||
final String label;
|
||||
}
|
||||
|
||||
Future<T?> showAppSelectionSheet<T>(
|
||||
BuildContext context, {
|
||||
required String title,
|
||||
required List<AppSelectionItem<T>> items,
|
||||
required T? selectedValue,
|
||||
}) async {
|
||||
final result = await showModalBottomSheet<T>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (sheetContext) {
|
||||
return SafeArea(
|
||||
top: false,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.fromLTRB(
|
||||
AppSpacing.md,
|
||||
AppSpacing.none,
|
||||
AppSpacing.md,
|
||||
AppSpacing.md,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(vertical: AppSpacing.lg),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.white,
|
||||
borderRadius: BorderRadius.circular(AppRadius.xl),
|
||||
border: Border.all(color: AppColors.borderSecondary),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.lg),
|
||||
child: Text(
|
||||
title,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontSize: 17,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.slate900,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
...items.map((item) {
|
||||
final isSelected = item.value == selectedValue;
|
||||
return _buildItem(
|
||||
sheetContext,
|
||||
item: item,
|
||||
isSelected: isSelected,
|
||||
);
|
||||
}),
|
||||
const SizedBox(height: AppSpacing.sm),
|
||||
const Divider(height: 1, color: AppColors.border),
|
||||
const SizedBox(height: AppSpacing.sm),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.lg),
|
||||
child: SizedBox(
|
||||
height: 48,
|
||||
child: AppButton(
|
||||
text: '取消',
|
||||
isOutlined: true,
|
||||
onPressed: () => Navigator.of(sheetContext).pop(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
Widget _buildItem<T>(
|
||||
BuildContext sheetContext, {
|
||||
required AppSelectionItem<T> item,
|
||||
required bool isSelected,
|
||||
}) {
|
||||
return InkWell(
|
||||
onTap: () => Navigator.of(sheetContext).pop(item.value),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: AppSpacing.lg,
|
||||
vertical: AppSpacing.md,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
item.label,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500,
|
||||
color: isSelected ? AppColors.blue600 : AppColors.slate800,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (isSelected)
|
||||
const Icon(Icons.check, size: 20, color: AppColors.blue600),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -8,12 +8,14 @@ class DetailHeaderActionItem<T> {
|
||||
required this.label,
|
||||
required this.icon,
|
||||
this.isDestructive = false,
|
||||
this.enabled = true,
|
||||
});
|
||||
|
||||
final T value;
|
||||
final String label;
|
||||
final IconData icon;
|
||||
final bool isDestructive;
|
||||
final bool enabled;
|
||||
}
|
||||
|
||||
class DetailHeaderActionMenu<T> extends StatefulWidget {
|
||||
@@ -141,7 +143,7 @@ class _DetailHeaderActionMenuState<T> extends State<DetailHeaderActionMenu<T>> {
|
||||
Widget _buildMenuItem(DetailHeaderActionItem<T> item) {
|
||||
final textColor = item.isDestructive
|
||||
? AppColors.red500
|
||||
: AppColors.slate700;
|
||||
: (item.enabled ? AppColors.slate700 : AppColors.slate400);
|
||||
final pressedColor = item.isDestructive
|
||||
? AppColors.feedbackErrorSurface
|
||||
: AppColors.surfaceInfoLight;
|
||||
@@ -152,9 +154,9 @@ class _DetailHeaderActionMenuState<T> extends State<DetailHeaderActionMenu<T>> {
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(AppRadius.md),
|
||||
splashColor: pressedColor,
|
||||
highlightColor: pressedColor,
|
||||
onTap: () => _handleSelect(item.value),
|
||||
splashColor: item.enabled ? pressedColor : Colors.transparent,
|
||||
highlightColor: item.enabled ? pressedColor : Colors.transparent,
|
||||
onTap: item.enabled ? () => _handleSelect(item.value) : null,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
|
||||
child: Row(
|
||||
|
||||
Reference in New Issue
Block a user