Files
biz_xfj_cloud/lib/pages/terminal/setting/widgets/setting_components.dart
T
2026-07-24 15:27:20 +08:00

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,
),
),
],
),
),
),
);
}
}