feat: add interactive widgets (Dialog, BottomSheet, Picker) with PageStandards

This commit is contained in:
Developer
2026-04-08 01:36:19 +08:00
parent 7693ab9e1e
commit c4f3b967f6
3 changed files with 269 additions and 0 deletions

View File

@@ -0,0 +1,76 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:mom_kitchen/src/standards/page_standards.dart';
class StandardBottomSheet extends StatelessWidget {
final String? title;
final Widget child;
final double? height;
const StandardBottomSheet({
super.key,
this.title,
required this.child,
this.height,
});
static Future<T?> show<T>({
required BuildContext context,
String? title,
required Widget child,
double? height,
bool isScrollControlled = false,
}) {
return showModalBottomSheet<T>(
context: context,
isScrollControlled: isScrollControlled,
backgroundColor: Colors.transparent,
builder: (context) => StandardBottomSheet(
title: title,
child: child,
height: height,
),
);
}
@override
Widget build(BuildContext context) {
final standards = PageStandards.of(context);
return Container(
height: height,
decoration: BoxDecoration(
color: standards.backgroundColor,
borderRadius: BorderRadius.vertical(
top: Radius.circular(standards.scaledRadius(20)),
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
margin: EdgeInsets.only(top: standards.scaledHeight(8)),
width: standards.scaledWidth(36),
height: standards.scaledHeight(4),
decoration: BoxDecoration(
color: standards.textColor.withOpacity(0.2),
borderRadius: BorderRadius.circular(standards.scaledRadius(2)),
),
),
if (title != null)
Padding(
padding: standards.scaledPadding(EdgeInsets.all(16)),
child: Text(
title!,
style: standards.textStyle.copyWith(
fontSize: standards.fontSize + 4,
fontWeight: FontWeight.w600,
),
),
),
Flexible(child: child),
],
),
);
}
}

View File

@@ -0,0 +1,78 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:mom_kitchen/src/standards/page_standards.dart';
class StandardDialog extends StatelessWidget {
final String title;
final String? message;
final String? confirmText;
final String? cancelText;
final VoidCallback? onConfirm;
final VoidCallback? onCancel;
final bool isDestructive;
const StandardDialog({
super.key,
required this.title,
this.message,
this.confirmText,
this.cancelText,
this.onConfirm,
this.onCancel,
this.isDestructive = false,
});
static Future<bool?> show(
BuildContext context, {
required String title,
String? message,
String? confirmText,
String? cancelText,
bool isDestructive = false,
}) {
return showCupertinoDialog<bool>(
context: context,
builder: (context) => StandardDialog(
title: title,
message: message,
confirmText: confirmText,
cancelText: cancelText,
isDestructive: isDestructive,
),
);
}
@override
Widget build(BuildContext context) {
final standards = PageStandards.of(context);
final l10n = standards.l10n;
return CupertinoAlertDialog(
title: Text(title, style: standards.textStyle),
content: message != null
? Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(message!, style: standards.textStyle),
)
: null,
actions: [
if (cancelText != null || onCancel != null)
CupertinoDialogAction(
onPressed: () {
Navigator.pop(context, false);
onCancel?.call();
},
child: Text(cancelText ?? l10n.cancel),
),
CupertinoDialogAction(
isDestructiveAction: isDestructive,
onPressed: () {
Navigator.pop(context, true);
onConfirm?.call();
},
child: Text(confirmText ?? l10n.confirm),
),
],
);
}
}

View File

@@ -0,0 +1,115 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:mom_kitchen/src/standards/page_standards.dart';
class StandardPickerItem<T> {
final T value;
final String label;
StandardPickerItem({
required this.value,
required this.label,
});
}
class StandardPicker<T> extends StatelessWidget {
final List<StandardPickerItem<T>> items;
final T? selectedValue;
final ValueChanged<T?>? onChanged;
final String? title;
const StandardPicker({
super.key,
required this.items,
this.selectedValue,
this.onChanged,
this.title,
});
static Future<T?> show<T>({
required BuildContext context,
required List<StandardPickerItem<T>> items,
T? selectedValue,
String? title,
}) async {
final standards = PageStandards.of(context);
return await showCupertinoModalPopup<T>(
context: context,
builder: (context) => Container(
height: standards.scaledHeight(300),
decoration: BoxDecoration(
color: standards.backgroundColor,
borderRadius: BorderRadius.vertical(
top: Radius.circular(standards.scaledRadius(20)),
),
),
child: Column(
children: [
if (title != null)
Container(
padding: standards.scaledPadding(EdgeInsets.all(16)),
child: Text(
title!,
style: standards.textStyle.copyWith(
fontSize: standards.fontSize + 4,
fontWeight: FontWeight.w600,
),
),
),
Expanded(
child: CupertinoPicker(
itemExtent: standards.scaledHeight(40),
scrollController: FixedExtentScrollController(
initialItem: selectedValue != null
? items.indexWhere((e) => e.value == selectedValue)
: 0,
),
onSelectedItemChanged: (index) {
Navigator.pop(context, items[index].value);
},
children: items
.map((item) => Center(
child: Text(
item.label,
style: standards.textStyle,
),
))
.toList(),
),
),
],
),
),
);
}
@override
Widget build(BuildContext context) {
final standards = PageStandards.of(context);
return Container(
height: standards.scaledHeight(200),
color: standards.backgroundColor,
child: CupertinoPicker(
itemExtent: standards.scaledHeight(40),
scrollController: FixedExtentScrollController(
initialItem: selectedValue != null
? items.indexWhere((e) => e.value == selectedValue)
: 0,
),
onSelectedItemChanged: (index) {
onChanged?.call(items[index].value);
},
children: items
.map((item) => Center(
child: Text(
item.label,
style: standards.textStyle,
),
))
.toList(),
),
);
}
}