435 lines
13 KiB
Dart
435 lines
13 KiB
Dart
import 'package:ducafe_ui_core/ducafe_ui_core.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:forui/forui.dart';
|
|
|
|
import 'package:pj_supermarket_cloud/common/index.dart';
|
|
|
|
class SettingGroup extends StatelessWidget {
|
|
const SettingGroup({super.key, required this.title, required this.child});
|
|
|
|
final String title;
|
|
final Widget child;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = FTheme.of(context);
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
title,
|
|
style: theme.typography.lg.copyWith(
|
|
color: XfjDesignTokens.ink,
|
|
fontWeight: FontWeight.w800,
|
|
),
|
|
),
|
|
SizedBox(height: 12.h),
|
|
Container(
|
|
clipBehavior: Clip.antiAlias,
|
|
decoration: BoxDecoration(
|
|
color: XfjDesignTokens.surface,
|
|
border: Border.all(color: XfjDesignTokens.line),
|
|
borderRadius: BorderRadius.circular(XfjDesignTokens.cardRadius),
|
|
),
|
|
child: child,
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class SettingSegmentedSelector<T> extends StatelessWidget {
|
|
const SettingSegmentedSelector({
|
|
super.key,
|
|
required this.label,
|
|
required this.value,
|
|
required this.options,
|
|
required this.onChanged,
|
|
this.showDivider = true,
|
|
});
|
|
|
|
final String label;
|
|
final T value;
|
|
final Map<T, String> options;
|
|
final ValueChanged<T> onChanged;
|
|
final bool showDivider;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = FTheme.of(context);
|
|
return Container(
|
|
constraints: BoxConstraints(minHeight: 72.h),
|
|
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 12.h),
|
|
decoration: BoxDecoration(
|
|
border: showDivider
|
|
? const Border(bottom: BorderSide(color: XfjDesignTokens.line))
|
|
: null,
|
|
),
|
|
child: Row(
|
|
children: [
|
|
SizedBox(
|
|
width: 230.w,
|
|
child: Text(
|
|
label,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: theme.typography.base.copyWith(
|
|
color: XfjDesignTokens.ink,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
SizedBox(width: 16.w),
|
|
Expanded(
|
|
child: Align(
|
|
alignment: Alignment.centerRight,
|
|
child: Container(
|
|
padding: EdgeInsets.all(4.w),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFF0F2F5),
|
|
borderRadius: BorderRadius.circular(
|
|
XfjDesignTokens.controlRadius,
|
|
),
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: options.entries.map((entry) {
|
|
final active = value == entry.key;
|
|
return Padding(
|
|
padding: EdgeInsets.only(
|
|
left: entry.key == options.keys.first ? 0 : 4.w,
|
|
),
|
|
child: InkWell(
|
|
onTap: () => onChanged(entry.key),
|
|
borderRadius: BorderRadius.circular(
|
|
XfjDesignTokens.controlRadius,
|
|
),
|
|
child: Container(
|
|
constraints: BoxConstraints(
|
|
minWidth: 112.w,
|
|
minHeight: 48.h,
|
|
),
|
|
padding: EdgeInsets.symmetric(
|
|
horizontal: 20.w * XfjDesignTokens.buttonSizeScale,
|
|
vertical: 6.h * XfjDesignTokens.buttonSizeScale,
|
|
),
|
|
alignment: Alignment.center,
|
|
decoration: BoxDecoration(
|
|
color: active ? Colors.white : Colors.transparent,
|
|
borderRadius: BorderRadius.circular(
|
|
XfjDesignTokens.controlRadius,
|
|
),
|
|
boxShadow:
|
|
active ? XfjDesignTokens.surfaceShadow : null,
|
|
),
|
|
child: Text(
|
|
entry.value,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: theme.typography.base.copyWith(
|
|
color: active
|
|
? XfjDesignTokens.primary
|
|
: XfjDesignTokens.muted,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}).toList(),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class SettingInfoRow extends StatelessWidget {
|
|
const SettingInfoRow({
|
|
super.key,
|
|
required this.icon,
|
|
required this.label,
|
|
this.value,
|
|
this.actionLabel,
|
|
this.onTap,
|
|
this.showDivider = true,
|
|
this.trailing,
|
|
this.subtitle,
|
|
});
|
|
|
|
final SvgAsset icon;
|
|
final String label;
|
|
final String? value;
|
|
final String? actionLabel;
|
|
final VoidCallback? onTap;
|
|
final bool showDivider;
|
|
final Widget? trailing;
|
|
final String? subtitle;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = FTheme.of(context);
|
|
final hasAction = actionLabel != null;
|
|
return InkWell(
|
|
onTap: onTap,
|
|
child: Container(
|
|
constraints: BoxConstraints(minHeight: subtitle == null ? 68.h : 80.h),
|
|
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 10.h),
|
|
decoration: BoxDecoration(
|
|
border: showDivider
|
|
? const Border(bottom: BorderSide(color: XfjDesignTokens.line))
|
|
: null,
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
width: 40.w,
|
|
height: 40.w,
|
|
alignment: Alignment.center,
|
|
decoration: BoxDecoration(
|
|
color: XfjDesignTokens.control,
|
|
borderRadius: BorderRadius.circular(
|
|
XfjDesignTokens.controlRadius,
|
|
),
|
|
),
|
|
child: FIcon(icon, color: XfjDesignTokens.muted, size: 22.sp),
|
|
),
|
|
SizedBox(width: 16.w),
|
|
SizedBox(
|
|
width: subtitle == null ? 230.w : 520.w,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Text(
|
|
label,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: theme.typography.base.copyWith(
|
|
color: XfjDesignTokens.ink,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
if (subtitle != null) ...[
|
|
SizedBox(height: 4.h),
|
|
Text(
|
|
subtitle!,
|
|
maxLines: 2,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: theme.typography.sm.copyWith(
|
|
color: XfjDesignTokens.muted,
|
|
),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
SizedBox(width: 16.w),
|
|
Expanded(
|
|
child: value == null
|
|
? const SizedBox.shrink()
|
|
: Text(
|
|
value!,
|
|
maxLines: subtitle == null ? 1 : 2,
|
|
overflow: TextOverflow.ellipsis,
|
|
textAlign: TextAlign.right,
|
|
style: theme.typography.base.copyWith(
|
|
color: XfjDesignTokens.muted,
|
|
fontFeatures: const [FontFeature.tabularFigures()],
|
|
),
|
|
),
|
|
),
|
|
if (trailing != null) ...[SizedBox(width: 16.w), trailing!],
|
|
if (hasAction) ...[
|
|
SizedBox(width: 16.w),
|
|
SizedBox(
|
|
width: 148.w,
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.end,
|
|
children: [
|
|
Flexible(
|
|
child: Text(
|
|
actionLabel!,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: theme.typography.base.copyWith(
|
|
color: XfjDesignTokens.primary,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
SizedBox(width: 6.w),
|
|
FIcon(
|
|
actionLabel == '复制'
|
|
? FAssets.icons.copy
|
|
: FAssets.icons.chevronRight,
|
|
color: XfjDesignTokens.primary,
|
|
size: 18.sp,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class SettingNotice extends StatelessWidget {
|
|
const SettingNotice({
|
|
super.key,
|
|
required this.text,
|
|
this.color = XfjDesignTokens.muted,
|
|
this.background = XfjDesignTokens.control,
|
|
this.borderColor,
|
|
this.icon,
|
|
});
|
|
|
|
final String text;
|
|
final Color color;
|
|
final Color background;
|
|
final Color? borderColor;
|
|
final SvgAsset? icon;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = FTheme.of(context);
|
|
return Container(
|
|
width: double.infinity,
|
|
padding: EdgeInsets.all(16.w),
|
|
decoration: BoxDecoration(
|
|
color: background,
|
|
border: borderColor == null ? null : Border.all(color: borderColor!),
|
|
borderRadius: BorderRadius.circular(XfjDesignTokens.controlRadius),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
if (icon != null) ...[
|
|
FIcon(icon!, color: color, size: 20.sp),
|
|
SizedBox(width: 10.w),
|
|
],
|
|
Expanded(
|
|
child: Text(
|
|
text,
|
|
style: theme.typography.sm.copyWith(color: color),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class SettingEmpty extends StatelessWidget {
|
|
const SettingEmpty({super.key, required this.message, this.icon});
|
|
|
|
final String message;
|
|
final SvgAsset? icon;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = FTheme.of(context);
|
|
return Center(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
FIcon(
|
|
icon ?? FAssets.icons.inbox,
|
|
color: XfjDesignTokens.muted,
|
|
size: 48.sp,
|
|
),
|
|
SizedBox(height: 12.h),
|
|
Text(
|
|
message,
|
|
style: theme.typography.base.copyWith(
|
|
color: XfjDesignTokens.muted,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class SettingActionButton extends StatelessWidget {
|
|
const SettingActionButton({
|
|
super.key,
|
|
required this.label,
|
|
required this.onTap,
|
|
this.icon,
|
|
this.primary = false,
|
|
this.danger = false,
|
|
this.enabled = true,
|
|
});
|
|
|
|
final String label;
|
|
final VoidCallback onTap;
|
|
final SvgAsset? icon;
|
|
final bool primary;
|
|
final bool danger;
|
|
final bool enabled;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = FTheme.of(context);
|
|
final foreground = danger
|
|
? XfjDesignTokens.danger
|
|
: primary
|
|
? Colors.white
|
|
: XfjDesignTokens.primary;
|
|
final background = primary
|
|
? XfjDesignTokens.primary
|
|
: danger
|
|
? const Color(0xFFFFF3F2)
|
|
: XfjDesignTokens.surface;
|
|
final sizeScale = XfjDesignTokens.buttonSizeScale;
|
|
return Opacity(
|
|
opacity: enabled ? 1 : 0.45,
|
|
child: InkWell(
|
|
onTap: enabled ? onTap : null,
|
|
borderRadius: BorderRadius.circular(XfjDesignTokens.controlRadius),
|
|
child: Container(
|
|
constraints: BoxConstraints(minHeight: 48.h),
|
|
padding: EdgeInsets.symmetric(
|
|
horizontal: 18.w * sizeScale,
|
|
vertical: 10.h * sizeScale,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: background,
|
|
border: Border.all(
|
|
color: primary
|
|
? XfjDesignTokens.primary
|
|
: danger
|
|
? XfjDesignTokens.danger.withValues(alpha: 0.35)
|
|
: XfjDesignTokens.line,
|
|
),
|
|
borderRadius: BorderRadius.circular(XfjDesignTokens.controlRadius),
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
if (icon != null) ...[
|
|
FIcon(icon!, color: foreground, size: 20.sp),
|
|
SizedBox(width: 8.w),
|
|
],
|
|
Text(
|
|
label,
|
|
style: theme.typography.base.copyWith(
|
|
color: foreground,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|