import 'dart:io'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:cashier_reserve/common/base/ui.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; /// 应用主体背景颜色 const Color appBackGroundColor = Color.fromARGB(255, 247, 250, 255); /// 主色调 const Color mdMainColor = Color(0xff2854BC); /// fix for Flutter 2.5 /// https://github.com/flutter/flutter/issues/77904 Brightness? getAppBrightness(bool isLight) { if (kIsWeb) { return null; } if (Platform.isIOS) { return isLight ? Brightness.dark : Brightness.light; } return isLight ? Brightness.light : Brightness.dark; } /// 微信风格Appbar AppBar makeWechatStyleAppbar(BuildContext context, String title, {List? actions, final Color backGroundColor = Colors.white, final Color textColor = Colors.black, final Brightness brightness = Brightness.light, bool centerTitle = false, PreferredSizeWidget? bottom}) { return AppBar( backgroundColor: backGroundColor, elevation: 0, centerTitle: centerTitle, systemOverlayStyle: SystemUiOverlayStyle( statusBarColor: Colors.transparent, statusBarIconBrightness: getAppBrightness(false), statusBarBrightness: getAppBrightness(false)), actions: actions, bottom: bottom, title: Text( title, style: TextStyle( fontSize: 17, color: textColor, fontWeight: FontWeight.bold), ), leading: IconButton( iconSize: 18, tooltip: "返回", color: Colors.black, icon: Icon( Icons.arrow_back_ios, color: textColor, ), onPressed: () { Navigator.pop(context); }, ), ); } /// 通用Appbar AppBar makeAppbar(BuildContext context, String? title, {List? actions, Color? backgroundColor, PreferredSizeWidget? bottom, final bool showBack = true, Widget? titleWidget, Widget? backWidget, VoidCallback? onBack, Key? key}) { backgroundColor ??= mdMainColor; return AppBar( key: key, backgroundColor: backgroundColor, elevation: 0, centerTitle: true, actions: actions, bottom: bottom, systemOverlayStyle: SystemUiOverlayStyle( statusBarColor: Colors.transparent, statusBarIconBrightness: getAppBrightness(false), statusBarBrightness: getAppBrightness(false)), title: titleWidget ?? Text( title!, style: const TextStyle( fontSize: 17, color: Colors.white, fontWeight: FontWeight.bold), ), leading: showBack ? (backWidget ?? IconButton( iconSize: 18, tooltip: "返回", color: Colors.white, splashColor: Colors.black, hoverColor: Colors.black, icon: const Icon(Icons.arrow_back_ios), onPressed: onBack ?? () { Navigator.pop(context); }, )) : null, ); } /// ListView 无感上拉加载 class ListViewPullUpLoadMore { final ScrollController scrollController; final Future Function() onLoadMore; bool _isLoading = false; ListViewPullUpLoadMore(this.scrollController, {required this.onLoadMore}) { scrollController.addListener(() { if (_isLoading) { return; } if (scrollController.offset == scrollController.position.maxScrollExtent) { /// 到底部,添加加载中标记,触发回调 _isLoading = true; onLoadMore().then((_) { /// 加载完毕,标记复位 _isLoading = false; }); } }); } } /// 绘制通用下划线 Widget makeDivider(BuildContext? context, {Color? color, EdgeInsetsGeometry? padding, double? height}) { color ??= Colors.black.withAlpha(50); return Padding( padding: padding ?? const EdgeInsets.only(left: 18.5, right: 18.5, top: 5, bottom: 5), child: Divider( color: color, height: height ?? 0.5, ), ); } /// 卡片容器 Widget makeCardContainer( {required BuildContext context, required Widget child, Key? key, EdgeInsetsGeometry? padding, EdgeInsetsGeometry? paddingIn, BorderRadius? borderRadius, Color? color, double? elevation, double? width, bool nullWidth = false}) { return SizedBox( key: key, width: nullWidth ? null : (width ?? MediaQuery.of(context).size.width), child: Padding( padding: padding ?? const EdgeInsets.only(left: 8, right: 8), child: Card( color: color, elevation: elevation ?? 0, shape: RoundedRectangleBorder( borderRadius: borderRadius ?? BorderRadius.circular(0), ), child: Padding( padding: paddingIn ?? const EdgeInsets.only(top: 10, bottom: 10), child: child, )))); } /// 通用页面加载动画 Widget makeLoadBody(BuildContext context) { return const Center( child: CircularProgressIndicator(), ); } /// 通用网络图片 Widget makeNetImage(BuildContext context, String? imageUrl, {BoxFit? fit, double? height, double? width}) { imageUrl ??= "https://image.xiaomfzs.com/xmf-app/user_icon.png"; if (kIsWeb) { return Image.network(imageUrl, fit: fit, height: height, width: width); } return CachedNetworkImage( height: height, width: width, imageUrl: imageUrl, fit: fit, placeholder: (context, url) { return Container( color: Colors.black.withAlpha(50), child: Center( child: Image.asset("images/loading.png"), )); }, errorWidget: (BuildContext context, String url, dynamic error) { return const Icon(Icons.error); }, ); } /// 通用头像 Widget makeUserAvatar(BuildContext context, String? imgUrl, {final double size = 46}) { return SizedBox( width: size, height: size, child: ClipRRect( borderRadius: BorderRadius.circular(10000.0), child: makeNetImage(context, imgUrl, fit: BoxFit.fill), ), ); } /// 底部按钮边距容器 Widget makeNavButtonPadding(Widget? widget) { return Padding( padding: const EdgeInsets.only(bottom: 14), child: widget, ); } /// 底部按钮 Widget makeBottomNavButton(String text, {required VoidCallback? onTap, Color color = mdMainColor, EdgeInsetsGeometry? padding, TextStyle? textStyle, double? elevation}) { return Padding( padding: padding ?? const EdgeInsets.only(left: 20, right: 20), child: ElevatedButton( style: TextButton.styleFrom( backgroundColor: color, elevation: elevation ?? 1), onPressed: onTap, child: Text( text, style: textStyle ?? const TextStyle(color: Colors.white, fontWeight: FontWeight.bold), ), ), ); } /// 收起键盘容器 Widget makeDismissKeyboardContainer(BuildContext context, Widget child, {VoidCallback? onDismissed}) { return GestureDetector( onTap: () { FocusScope.of(context).requestFocus(FocusNode()); if (onDismissed != null) { onDismissed(); } }, child: child, ); } /// 底部弹出按钮 Future showBottomMenu( BuildContext context, Map items) async { return await showCupertinoModalPopup( context: context, builder: (ctx) { return CupertinoActionSheet( cancelButton: CupertinoActionSheetAction( onPressed: () { Navigator.pop(context); }, child: const Text( '取消', style: TextStyle(fontSize: 14, color: Colors.red), ), ), actions: [ for (var kv in (items.entries)) CupertinoActionSheetAction( onPressed: () { Navigator.pop(context, kv.key); }, child: Text( kv.value, style: const TextStyle(fontSize: 14, color: Colors.black), ), ), ], ); }); } /// 日期选择器 Future showMyDatePicker( BuildContext context, CupertinoDatePickerMode mode, {DateTime? defaultTime, DateTime? minTime, DateTime? maxTime}) async { final DateTime? date = await showModalBottomSheet( context: context, builder: (context) { DateTime pickedDate = DateTime.now(); return SizedBox( height: 250, child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ CupertinoButton( child: const Text('取消'), onPressed: () { Navigator.of(context).pop(); }, ), CupertinoButton( child: const Text('确认'), onPressed: () { Navigator.pop(context, pickedDate); }, ), ], ), const Divider( height: 0, thickness: 1, ), Expanded( child: CupertinoApp( debugShowCheckedModeBanner: false, locale: const Locale('zh', 'CN'), localizationsDelegates: const [ GlobalCupertinoLocalizations.delegate, ], supportedLocales: const [ Locale('zh', 'CN'), Locale('en', 'US'), ], home: CupertinoDatePicker( mode: mode, initialDateTime: defaultTime ?? DateTime.now(), minimumDate: minTime, maximumDate: maxTime, onDateTimeChanged: (DateTime dateTime) { pickedDate = dateTime; }, ), ), ), ], ), ); }, ); return date; } /// appBar Search Widget PreferredSizeWidget makeAppBarSearchWidget( BuildContext context, String tipText, ValueChanged onSubmitted, { double height = 56, double? width, TextInputType? textInputType, List? inputFormatters, ValueChanged? onChanged, TextEditingController? controller, }) { return PreferredSize( child: SizedBox( height: height, child: Padding( padding: const EdgeInsets.only(top: 5, left: 10, right: 10, bottom: 10), child: Container( width: width ?? MediaQuery.of(context).size.width, decoration: BoxDecoration( color: Colors.white.withAlpha(100), borderRadius: BorderRadius.circular(20), ), child: Row( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ Container( margin: const EdgeInsets.only(left: 15), child: const Icon( Icons.search, color: Colors.black45, size: 20, ), ), Container( margin: const EdgeInsets.only(left: 5), width: MediaQuery.of(context).size.width - 15 * 2 - 15 - 20 - 5 - (width ?? 0), child: TextField( controller: controller, textInputAction: TextInputAction.search, onSubmitted: onSubmitted, keyboardType: textInputType, inputFormatters: inputFormatters, onChanged: onChanged, decoration: InputDecoration( hintText: tipText, hintStyle: const TextStyle( fontSize: 15, color: Colors.black45), border: InputBorder.none, ), ), ), ], )), ), ), preferredSize: const Size(0, 42)); } Widget makeSearchWidget( BuildContext context, String hintText, ValueChanged onSubmitted) { return Padding( padding: const EdgeInsets.only(top: 5, left: 0, right: 0, bottom: 14), child: Container( height: 42, width: MediaQuery.of(context).size.width, decoration: BoxDecoration( color: const Color.fromARGB(255, 247, 250, 255), borderRadius: BorderRadius.circular(20), ), child: Row( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ Container( margin: const EdgeInsets.only(left: 15), child: const Icon( Icons.search, color: Colors.black45, size: 20, ), ), Container( margin: const EdgeInsets.only(left: 5), width: MediaQuery.of(context).size.width - 15 * 2 - 82, child: TextField( textInputAction: TextInputAction.search, onSubmitted: onSubmitted, onChanged: (str) { if (str == "") { onSubmitted(""); } }, decoration: InputDecoration( hintText: hintText, hintStyle: const TextStyle(fontSize: 15, color: Colors.black45), border: InputBorder.none, ), ), ), ], )), ); } class AlwaysDisabledFocusNode extends FocusNode { @override bool get hasFocus => false; }