import 'package:flutter/material.dart'; import '../app/theme.dart'; /// 统一底部弹窗封装:一次性解决「无把手 / 无安全区 / 无限高 / 保存按钮埋在滚动区」。 /// /// - 顶部拖拽把手([BottomSheetThemeData.showDragHandle] 已全局兜底,这里显式再设一次)。 /// - 安全区(不被手势条遮挡)。 /// - 限宽 640 居中(横屏不铺满)。 /// - [footer] 吸底操作栏始终可见,[body] 内部独立滚动。 /// /// 详见 docs/手机端UI优化方案-v2.md §8.4。 Future showAppBottomSheet({ required BuildContext context, required Widget body, Widget? footer, String? title, double heightFactor = 0.92, }) { return showModalBottomSheet( context: context, isScrollControlled: true, showDragHandle: true, useSafeArea: true, constraints: BoxConstraints( maxWidth: 640, maxHeight: MediaQuery.sizeOf(context).height * heightFactor, ), builder: (ctx) => Column( mainAxisSize: MainAxisSize.min, children: [ if (title != null) Padding( padding: const EdgeInsets.fromLTRB(20, 4, 20, 12), child: Align( alignment: Alignment.centerLeft, child: Text( title, style: Theme.of(ctx) .textTheme .titleLarge ?.copyWith(fontWeight: FontWeight.bold), ), ), ), Flexible( child: SingleChildScrollView( padding: EdgeInsets.fromLTRB( 20, 0, 20, 20 + MediaQuery.viewInsetsOf(ctx).bottom, ), child: body, ), ), if (footer != null) Container( decoration: BoxDecoration( color: Theme.of(ctx).colorScheme.surface, border: Border( top: BorderSide( color: Theme.of(ctx).colorScheme.outlineVariant, ), ), boxShadow: AppShadows.lg, ), padding: const EdgeInsets.fromLTRB(20, 12, 20, 12), // 吸底栏不受滚动影响,但底部手势条区域交给 SafeArea 处理。 child: SafeArea(top: false, child: footer), ), ], ), ); }