29 changed files with 1810 additions and 172 deletions
Unified View
Diff Options
-
BINassets/images/discover_nol.png
-
BINassets/images/discover_pre.png
-
BINassets/images/home_nol.png
-
BINassets/images/home_pre.png
-
BINassets/images/message_nol.png
-
BINassets/images/message_pre.png
-
BINassets/images/mine_nol.png
-
BINassets/images/mine_pre.png
-
13lib/extension/ex_context.dart
-
102lib/extension/ex_date.dart
-
44lib/extension/ex_int.dart
-
17lib/extension/ex_string.dart
-
936lib/extension/ex_widget.dart
-
17lib/generated/assets.dart
-
5lib/main.dart
-
21lib/pages/discover/discover_page.dart
-
24lib/pages/home/home_page.dart
-
91lib/pages/main/main_page.dart
-
182lib/pages/main/tabbar/main_tab_bar.dart
-
77lib/pages/main/tabbar/main_tab_btn.dart
-
13lib/pages/main/tabbar/main_tab_item.dart
-
45lib/pages/main_page.dart
-
22lib/pages/message/message_page.dart
-
2lib/pages/mine/login_controller.dart
-
21lib/pages/mine/mine_page.dart
-
2lib/pages/mine/user_info_controller.dart
-
39lib/widget/double_tap_to_exit_widget.dart
-
308pubspec.lock
-
1pubspec.yaml
@ -0,0 +1,13 @@ |
|||||
|
import 'package:flutter/material.dart'; |
||||
|
|
||||
|
extension BuildContextExtension on BuildContext { |
||||
|
MediaQueryData get mediaQuery => MediaQuery.of(this); |
||||
|
|
||||
|
double get topPadding => mediaQuery.padding.top; |
||||
|
|
||||
|
double get bottomPadding => mediaQuery.padding.bottom; |
||||
|
|
||||
|
double get bottomInsets => mediaQuery.viewInsets.bottom; |
||||
|
|
||||
|
EdgeInsets get viewInsets => mediaQuery.viewInsets; |
||||
|
} |
||||
@ -0,0 +1,102 @@ |
|||||
|
import 'package:easy_localization/easy_localization.dart'; |
||||
|
extension DateTimeIntExt on int { |
||||
|
String get formatExpired { |
||||
|
if (this == -1) { |
||||
|
return "Perpetually valid".tr(); |
||||
|
} |
||||
|
final now = DateTime.now(); |
||||
|
final date = DateTime.fromMillisecondsSinceEpoch(this); |
||||
|
final difference = date.difference(now); |
||||
|
if (difference.inHours > 24) { |
||||
|
return "{} Days".tr(args: ["${difference.inDays}"]); |
||||
|
} |
||||
|
if (difference.inHours > 1) { |
||||
|
return "{} Hours".tr(args: ["${difference.inHours}"]); |
||||
|
} |
||||
|
return "<1 Hours".tr(); |
||||
|
} |
||||
|
|
||||
|
String get formatTimeago { |
||||
|
int value = (this >= 1000000000 && this <= 9999999999) ? this * 1000 : this; |
||||
|
// 将时间戳转换为DateTime对象 |
||||
|
final date = DateTime.fromMillisecondsSinceEpoch(value, isUtc: true); |
||||
|
final now = DateTime.now(); |
||||
|
final difference = now.difference(date); |
||||
|
if ((date.year - 1) >= now.year) { |
||||
|
return "${date.year}-${date.month}-${date.day}"; |
||||
|
} else if (difference.inDays > 1) { |
||||
|
return "${date.month}-${date.day}"; |
||||
|
} else if (difference.inDays == 1) { |
||||
|
return 'yesterday'.tr(); |
||||
|
} else if (difference.inDays < 1 && difference.inHours >= 3) { |
||||
|
return "${date.hour}:${date.minute}"; |
||||
|
} else if (difference.inHours >= 1) { |
||||
|
return '{} hours ago'.tr(args: ["${difference.inHours}"]); |
||||
|
} else if (difference.inMinutes >= 1) { |
||||
|
return '{} minute ago'.tr(args: ["${difference.inMinutes}"]); |
||||
|
} else { |
||||
|
return 'just now'.tr(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// hours 时区偏移, 0 为本地时间 |
||||
|
String formatString({ |
||||
|
String format = "yyyy.MM.dd HH:mm:ss", |
||||
|
int hours = 0, |
||||
|
bool isUtc = false, |
||||
|
String? locale, |
||||
|
}) { |
||||
|
int value = (this >= 1000000000 && this <= 9999999999) ? this * 1000 : this; |
||||
|
// 将时间戳转换为DateTime对象 |
||||
|
final date = DateTime.fromMillisecondsSinceEpoch(value, |
||||
|
isUtc: hours > 0 ? true : isUtc); |
||||
|
if (hours != 0) { |
||||
|
Duration utcMinus3Offset = Duration(hours: hours); |
||||
|
return DateFormat(format, locale ?? "en") |
||||
|
.format(date.add(utcMinus3Offset)); |
||||
|
} |
||||
|
return DateFormat(format, locale ?? "en").format(date); |
||||
|
} |
||||
|
|
||||
|
/// hours 时区偏移, 0 为本地时间 |
||||
|
// String formatStringToLoacl( |
||||
|
// {String format = "yyyy-MM-dd HH:mm:ss", int hours = 3}) { |
||||
|
// int value = (this >= 1000000000 && this <= 9999999999) ? this * 1000 : this; |
||||
|
// // 将时间戳转换为DateTime对象 |
||||
|
// // final date = DateTime.fromMillisecondsSinceEpoch(value, isUtc: true); |
||||
|
// final date = DateTime.fromMicrosecondsSinceEpoch(value * 1000); |
||||
|
// // if (hours != 0) { |
||||
|
// // Duration utcMinus3Offset = Duration(hours: hours); |
||||
|
// // return DateFormat(format).format(date.add(utcMinus3Offset)); |
||||
|
// // } |
||||
|
// return SpUtil().dateFormatOfLocal2(DateFormat(format).format(date)); |
||||
|
// } |
||||
|
} |
||||
|
|
||||
|
extension DateTimeExt on DateTime { |
||||
|
String get formatUTC3String { |
||||
|
DateFormat dateFormat = DateFormat('yyyy.MM.dd HH:mm:ss', "en"); |
||||
|
|
||||
|
// 格式化 DateTime 对象 |
||||
|
String formattedDate = |
||||
|
dateFormat.format(toUtc().add(const Duration(hours: 3))); |
||||
|
|
||||
|
// 返回格式化后的字符串 |
||||
|
return '$formattedDate (UTC+3)'; |
||||
|
} |
||||
|
|
||||
|
String formatStringWithFormat(String fromat) { |
||||
|
DateFormat dateFormat = DateFormat(fromat, "en"); |
||||
|
return dateFormat.format(this); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
extension DateTimeStrExt on String { |
||||
|
DateTime get formatUtcTime { |
||||
|
return DateFormat('yyyy-MM-dd HH:mm', "en").parse(this).toUtc(); |
||||
|
} |
||||
|
|
||||
|
DateTime get formatLocalTime { |
||||
|
return DateFormat('yyyy-MM-dd HH:mm', "en").parse(this).toLocal(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
extension IntExt on int { |
||||
|
String get formatRank { |
||||
|
if (this < 1000) { |
||||
|
return toString(); |
||||
|
} else if (this < 1000000) { |
||||
|
return '${_formatResult((this / 1000))}K'; |
||||
|
} else if (this < 1000000000) { |
||||
|
return '${_formatResult(this / 1000000)}M'; |
||||
|
} |
||||
|
return '${_formatResult(this / 1000000000)}B'; |
||||
|
} |
||||
|
|
||||
|
String get formatUSDString { |
||||
|
return (this / 100).toStringAsFixed(2); |
||||
|
} |
||||
|
|
||||
|
String _formatResult(double value) { |
||||
|
value.toStringAsFixed(2); |
||||
|
final String resultStr = value.toString(); //value.toStringAsFixed(2); |
||||
|
final index = resultStr.lastIndexOf('.') + 3; |
||||
|
String resultVlueStr = resultStr; |
||||
|
if (index < resultStr.length) { |
||||
|
resultVlueStr = resultStr.substring(0, index); |
||||
|
} |
||||
|
return resultVlueStr.endsWith('.0') |
||||
|
? value.toStringAsFixed(0) |
||||
|
: resultVlueStr; |
||||
|
} |
||||
|
|
||||
|
String get formatDurationFromSeconds{ |
||||
|
Duration duration = Duration(seconds: this); |
||||
|
String twoDigits(int n) => n.toString().padLeft(2, '0'); |
||||
|
String minutes = twoDigits(duration.inMinutes.remainder(60)); |
||||
|
String seconds = twoDigits(duration.inSeconds.remainder(60)); |
||||
|
|
||||
|
if (duration.inHours > 0) { |
||||
|
String hours = duration.inHours.toString(); |
||||
|
return "$hours:$minutes:$seconds"; |
||||
|
} else { |
||||
|
return "$minutes:$seconds"; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
import 'package:easy_localization/easy_localization.dart'; |
||||
|
|
||||
|
extension DateTimeStringExt on String { |
||||
|
String formatTimeString({String format = "yyyy.MM.dd HH:mm:ss"}) { |
||||
|
final DateTime? time = DateTime.tryParse(this); |
||||
|
if (time == null) return ""; |
||||
|
return DateFormat(format, "en").format(time); |
||||
|
} |
||||
|
|
||||
|
int get toInt { |
||||
|
return int.tryParse(this) ?? 0; |
||||
|
} |
||||
|
|
||||
|
Map<String, String> get queryParameters { |
||||
|
return Uri.tryParse(this)?.queryParameters ?? {}; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,936 @@ |
|||||
|
import 'package:flutter/gestures.dart'; |
||||
|
import 'package:flutter/material.dart'; |
||||
|
|
||||
|
/// 手势 tap |
||||
|
typedef GestureOnTapChangeCallback = void Function<T>(T val); |
||||
|
|
||||
|
/// 扩展 Widget |
||||
|
extension ExWidget on Widget { |
||||
|
/// 对齐 |
||||
|
Widget align( |
||||
|
AlignmentGeometry alignment, { |
||||
|
Key? key, |
||||
|
}) => |
||||
|
Align( |
||||
|
key: key, |
||||
|
alignment: alignment, |
||||
|
child: this, |
||||
|
); |
||||
|
|
||||
|
/// 对齐 中间 |
||||
|
Widget alignCenter() => align(Alignment.center); |
||||
|
|
||||
|
/// 对齐 左边 |
||||
|
Widget alignLeftCenter() => align(Alignment.centerLeft); |
||||
|
|
||||
|
/// 对齐 右边 |
||||
|
Widget alignRightCenter() => align(Alignment.centerRight); |
||||
|
|
||||
|
/// 对齐 顶部 |
||||
|
Widget alignTopCenter() => align(Alignment.topCenter); |
||||
|
|
||||
|
/// 对齐 底部 |
||||
|
Widget alignBottomCenter() => align(Alignment.bottomCenter); |
||||
|
|
||||
|
Widget alignLeftTop() => align(Alignment.topLeft); |
||||
|
|
||||
|
Widget alignRightTop() => align(Alignment.topRight); |
||||
|
|
||||
|
Widget alignLeftBottom() => align(Alignment.bottomLeft); |
||||
|
|
||||
|
Widget alignRightBottom() => align(Alignment.bottomRight); |
||||
|
|
||||
|
// 比例布局 |
||||
|
Widget aspectRatio({ |
||||
|
Key? key, |
||||
|
required double aspectRatio, |
||||
|
}) => |
||||
|
AspectRatio( |
||||
|
key: key, |
||||
|
aspectRatio: aspectRatio, |
||||
|
child: this, |
||||
|
); |
||||
|
|
||||
|
/// 背景颜色 |
||||
|
Widget backgroundColor( |
||||
|
Color color, { |
||||
|
Key? key, |
||||
|
}) => |
||||
|
DecoratedBox( |
||||
|
key: key, |
||||
|
decoration: BoxDecoration(color: color), |
||||
|
child: this, |
||||
|
); |
||||
|
|
||||
|
/// 背景图片 |
||||
|
Widget backgroundImage( |
||||
|
DecorationImage image, { |
||||
|
Key? key, |
||||
|
}) => |
||||
|
DecoratedBox( |
||||
|
key: key, |
||||
|
decoration: BoxDecoration(image: image), |
||||
|
child: this, |
||||
|
); |
||||
|
|
||||
|
/// 边框 |
||||
|
Widget border({ |
||||
|
Key? key, |
||||
|
double? all, |
||||
|
double? left, |
||||
|
double? right, |
||||
|
double? top, |
||||
|
double? bottom, |
||||
|
Color color = const Color(0xFF000000), |
||||
|
BorderStyle style = BorderStyle.solid, |
||||
|
}) { |
||||
|
BoxDecoration decoration = BoxDecoration( |
||||
|
border: Border( |
||||
|
left: (left ?? all) == null |
||||
|
? BorderSide.none |
||||
|
: BorderSide(color: color, width: left ?? all ?? 0, style: style), |
||||
|
right: (right ?? all) == null |
||||
|
? BorderSide.none |
||||
|
: BorderSide(color: color, width: right ?? all ?? 0, style: style), |
||||
|
top: (top ?? all) == null |
||||
|
? BorderSide.none |
||||
|
: BorderSide(color: color, width: top ?? all ?? 0, style: style), |
||||
|
bottom: (bottom ?? all) == null |
||||
|
? BorderSide.none |
||||
|
: BorderSide(color: color, width: bottom ?? all ?? 0, style: style), |
||||
|
), |
||||
|
); |
||||
|
return DecoratedBox( |
||||
|
key: key, |
||||
|
decoration: decoration, |
||||
|
child: this, |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
/// 圆角 |
||||
|
Widget borderRadius({ |
||||
|
Key? key, |
||||
|
double? all, |
||||
|
double? topLeft, |
||||
|
double? topRight, |
||||
|
double? bottomLeft, |
||||
|
double? bottomRight, |
||||
|
}) { |
||||
|
BoxDecoration decoration = BoxDecoration( |
||||
|
borderRadius: BorderRadius.only( |
||||
|
topLeft: Radius.circular(topLeft ?? all ?? 0.0), |
||||
|
topRight: Radius.circular(topRight ?? all ?? 0.0), |
||||
|
bottomLeft: Radius.circular(bottomLeft ?? all ?? 0.0), |
||||
|
bottomRight: Radius.circular(bottomRight ?? all ?? 0.0), |
||||
|
), |
||||
|
); |
||||
|
return DecoratedBox( |
||||
|
key: key, |
||||
|
decoration: decoration, |
||||
|
child: this, |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
/// 阴影 |
||||
|
Widget boxShadow({ |
||||
|
Key? key, |
||||
|
Color color = const Color(0xFF000000), |
||||
|
Offset offset = Offset.zero, |
||||
|
double blurRadius = 0.0, |
||||
|
double spreadRadius = 0.0, |
||||
|
}) { |
||||
|
BoxDecoration decoration = BoxDecoration( |
||||
|
boxShadow: [ |
||||
|
BoxShadow( |
||||
|
color: color, |
||||
|
blurRadius: blurRadius, |
||||
|
spreadRadius: spreadRadius, |
||||
|
offset: offset, |
||||
|
), |
||||
|
], |
||||
|
); |
||||
|
return DecoratedBox( |
||||
|
key: key, |
||||
|
decoration: decoration, |
||||
|
child: this, |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
Widget card({ |
||||
|
Key? key, |
||||
|
double? radius, |
||||
|
Color? color, |
||||
|
Color? shadowColor, |
||||
|
double? blurRadius, |
||||
|
}) => |
||||
|
Container( |
||||
|
decoration: BoxDecoration( |
||||
|
color: color ?? Colors.white, |
||||
|
borderRadius: BorderRadius.all( |
||||
|
Radius.circular(radius ?? 5), |
||||
|
), |
||||
|
boxShadow: [ |
||||
|
BoxShadow( |
||||
|
// x偏移量 | y偏移量 |
||||
|
offset: const Offset(0, 3), |
||||
|
color: shadowColor ?? Colors.black.withOpacity(0.15), |
||||
|
// 阴影模糊半径 |
||||
|
blurRadius: blurRadius ?? 8, |
||||
|
// 阴影扩散半径 |
||||
|
spreadRadius: 0, |
||||
|
blurStyle: BlurStyle.normal, |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
child: this, |
||||
|
); |
||||
|
|
||||
|
// 居中 |
||||
|
Widget center({ |
||||
|
Key? key, |
||||
|
double? widthFactor, |
||||
|
double? heightFactor, |
||||
|
}) => |
||||
|
Center( |
||||
|
key: key, |
||||
|
widthFactor: widthFactor, |
||||
|
heightFactor: heightFactor, |
||||
|
child: this, |
||||
|
); |
||||
|
|
||||
|
/// 裁剪 oval |
||||
|
Widget clipOval({Key? key}) => ClipOval( |
||||
|
key: key, |
||||
|
child: this, |
||||
|
); |
||||
|
|
||||
|
/// 裁剪 rect |
||||
|
Widget clipRect({ |
||||
|
Key? key, |
||||
|
CustomClipper<Rect>? clipper, |
||||
|
Clip clipBehavior = Clip.hardEdge, |
||||
|
}) => |
||||
|
ClipRect( |
||||
|
key: key, |
||||
|
clipper: clipper, |
||||
|
clipBehavior: clipBehavior, |
||||
|
child: this, |
||||
|
); |
||||
|
|
||||
|
/// 裁剪圆角 |
||||
|
Widget clipRRect({ |
||||
|
Key? key, |
||||
|
double? all, |
||||
|
double? topLeft, |
||||
|
double? topRight, |
||||
|
double? bottomLeft, |
||||
|
double? bottomRight, |
||||
|
CustomClipper<RRect>? clipper, |
||||
|
Clip clipBehavior = Clip.antiAlias, |
||||
|
}) => |
||||
|
ClipRRect( |
||||
|
key: key, |
||||
|
clipper: clipper, |
||||
|
clipBehavior: clipBehavior, |
||||
|
borderRadius: BorderRadius.only( |
||||
|
topLeft: Radius.circular(topLeft ?? all ?? 0.0), |
||||
|
topRight: Radius.circular(topRight ?? all ?? 0.0), |
||||
|
bottomLeft: Radius.circular(bottomLeft ?? all ?? 0.0), |
||||
|
bottomRight: Radius.circular(bottomRight ?? all ?? 0.0), |
||||
|
), |
||||
|
child: this, |
||||
|
); |
||||
|
|
||||
|
/// 约束 |
||||
|
// Widget constrained({ |
||||
|
// Key? key, |
||||
|
// double? width, |
||||
|
// double? height, |
||||
|
// double minWidth = 0.0, |
||||
|
// double maxWidth = double.infinity, |
||||
|
// double minHeight = 0.0, |
||||
|
// double maxHeight = double.infinity, |
||||
|
// }) { |
||||
|
// BoxConstraints constraints = BoxConstraints( |
||||
|
// minWidth: minWidth, |
||||
|
// maxWidth: maxWidth, |
||||
|
// minHeight: minHeight, |
||||
|
// maxHeight: maxHeight, |
||||
|
// ); |
||||
|
// constraints = (width != null || height != null) |
||||
|
// ? constraints.tighten(width: width, height: height) |
||||
|
// : constraints; |
||||
|
// return ConstrainedBox( |
||||
|
// key: key, |
||||
|
// constraints: constraints, |
||||
|
// child: this, |
||||
|
// ); |
||||
|
// } |
||||
|
|
||||
|
// 取消父级约束 |
||||
|
// Widget unconstrained({ |
||||
|
// Key? key, |
||||
|
// TextDirection? textDirection, |
||||
|
// AlignmentGeometry alignment = Alignment.center, |
||||
|
// Axis? constrainedAxis, |
||||
|
// Clip clipBehavior = Clip.none, |
||||
|
// }) => |
||||
|
// UnconstrainedBox( |
||||
|
// key: key, |
||||
|
// textDirection: textDirection, |
||||
|
// alignment: alignment, |
||||
|
// constrainedAxis: constrainedAxis, |
||||
|
// clipBehavior: clipBehavior, |
||||
|
// child: this, |
||||
|
// ); |
||||
|
|
||||
|
/// 盒子装饰器 |
||||
|
Widget decorated({ |
||||
|
Key? key, |
||||
|
Color? color, |
||||
|
DecorationImage? image, |
||||
|
BoxBorder? border, |
||||
|
BorderRadius? borderRadius, |
||||
|
List<BoxShadow>? boxShadow, |
||||
|
Gradient? gradient, |
||||
|
BlendMode? backgroundBlendMode, |
||||
|
BoxShape shape = BoxShape.rectangle, |
||||
|
DecorationPosition position = DecorationPosition.background, |
||||
|
}) { |
||||
|
BoxDecoration decoration = BoxDecoration( |
||||
|
color: color, |
||||
|
image: image, |
||||
|
border: border, |
||||
|
borderRadius: borderRadius, |
||||
|
boxShadow: boxShadow, |
||||
|
gradient: gradient, |
||||
|
backgroundBlendMode: backgroundBlendMode, |
||||
|
shape: shape, |
||||
|
); |
||||
|
return DecoratedBox( |
||||
|
key: key, |
||||
|
decoration: decoration, |
||||
|
position: position, |
||||
|
child: this, |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
/// elevation |
||||
|
Widget elevation( |
||||
|
double elevation, { |
||||
|
Key? key, |
||||
|
BorderRadiusGeometry borderRadius = BorderRadius.zero, |
||||
|
Color shadowColor = const Color(0xFF000000), |
||||
|
}) => |
||||
|
Material( |
||||
|
key: key, |
||||
|
color: Colors.transparent, |
||||
|
elevation: elevation, |
||||
|
borderRadius: borderRadius, |
||||
|
shadowColor: shadowColor, |
||||
|
child: this, |
||||
|
); |
||||
|
|
||||
|
/// expanded 撑满 |
||||
|
Widget expanded({ |
||||
|
Key? key, |
||||
|
int flex = 1, |
||||
|
}) => |
||||
|
Expanded( |
||||
|
key: key, |
||||
|
flex: flex, |
||||
|
child: this, |
||||
|
); |
||||
|
|
||||
|
// Widget fittedBox({ |
||||
|
// Key? key, |
||||
|
// BoxFit fit = BoxFit.contain, |
||||
|
// AlignmentGeometry alignment = Alignment.centerLeft, |
||||
|
// Clip clipBehavior = Clip.none, |
||||
|
// }) => |
||||
|
// FittedBox( |
||||
|
// key: key, |
||||
|
// fit: fit, |
||||
|
// alignment: alignment, |
||||
|
// clipBehavior: clipBehavior, |
||||
|
// child: this, |
||||
|
// ); |
||||
|
|
||||
|
/// 弹性布局 flexible |
||||
|
Widget flexible({ |
||||
|
Key? key, |
||||
|
int flex = 1, |
||||
|
FlexFit fit = FlexFit.loose, |
||||
|
}) => |
||||
|
Flexible( |
||||
|
key: key, |
||||
|
flex: flex, |
||||
|
fit: fit, |
||||
|
child: this, |
||||
|
); |
||||
|
|
||||
|
// |
||||
|
// Widget fractionallySizedBox({ |
||||
|
// Key? key, |
||||
|
// AlignmentGeometry alignment = Alignment.center, |
||||
|
// double? widthFactor, |
||||
|
// double? heightFactor, |
||||
|
// }) => |
||||
|
// FractionallySizedBox( |
||||
|
// key: key, |
||||
|
// alignment: alignment, |
||||
|
// widthFactor: widthFactor, |
||||
|
// heightFactor: heightFactor, |
||||
|
// child: this, |
||||
|
// ); |
||||
|
|
||||
|
/// 手势 |
||||
|
// Widget gestures({ |
||||
|
// Key? key, |
||||
|
// GestureOnTapChangeCallback? onTapChange, |
||||
|
// GestureTapDownCallback? onTapDown, |
||||
|
// GestureTapUpCallback? onTapUp, |
||||
|
// GestureTapCallback? onTap, |
||||
|
// GestureTapCancelCallback? onTapCancel, |
||||
|
// GestureTapDownCallback? onSecondaryTapDown, |
||||
|
// GestureTapUpCallback? onSecondaryTapUp, |
||||
|
// GestureTapCancelCallback? onSecondaryTapCancel, |
||||
|
// GestureTapCallback? onDoubleTap, |
||||
|
// GestureLongPressCallback? onLongPress, |
||||
|
// GestureLongPressStartCallback? onLongPressStart, |
||||
|
// GestureLongPressMoveUpdateCallback? onLongPressMoveUpdate, |
||||
|
// GestureLongPressUpCallback? onLongPressUp, |
||||
|
// GestureLongPressEndCallback? onLongPressEnd, |
||||
|
// GestureDragDownCallback? onVerticalDragDown, |
||||
|
// GestureDragStartCallback? onVerticalDragStart, |
||||
|
// GestureDragUpdateCallback? onVerticalDragUpdate, |
||||
|
// GestureDragEndCallback? onVerticalDragEnd, |
||||
|
// GestureDragCancelCallback? onVerticalDragCancel, |
||||
|
// GestureDragDownCallback? onHorizontalDragDown, |
||||
|
// GestureDragStartCallback? onHorizontalDragStart, |
||||
|
// GestureDragUpdateCallback? onHorizontalDragUpdate, |
||||
|
// GestureDragEndCallback? onHorizontalDragEnd, |
||||
|
// GestureDragCancelCallback? onHorizontalDragCancel, |
||||
|
// GestureDragDownCallback? onPanDown, |
||||
|
// GestureDragStartCallback? onPanStart, |
||||
|
// GestureDragUpdateCallback? onPanUpdate, |
||||
|
// GestureDragEndCallback? onPanEnd, |
||||
|
// GestureDragCancelCallback? onPanCancel, |
||||
|
// GestureScaleStartCallback? onScaleStart, |
||||
|
// GestureScaleUpdateCallback? onScaleUpdate, |
||||
|
// GestureScaleEndCallback? onScaleEnd, |
||||
|
// GestureForcePressStartCallback? onForcePressStart, |
||||
|
// GestureForcePressPeakCallback? onForcePressPeak, |
||||
|
// GestureForcePressUpdateCallback? onForcePressUpdate, |
||||
|
// GestureForcePressEndCallback? onForcePressEnd, |
||||
|
// HitTestBehavior? behavior, |
||||
|
// bool excludeFromSemantics = false, |
||||
|
// DragStartBehavior dragStartBehavior = DragStartBehavior.start, |
||||
|
// }) => |
||||
|
// GestureDetector( |
||||
|
// key: key, |
||||
|
// onTapDown: (TapDownDetails tapDownDetails) { |
||||
|
// if (onTapDown != null) onTapDown(tapDownDetails); |
||||
|
// if (onTapChange != null) onTapChange(true); |
||||
|
// }, |
||||
|
// onTapCancel: () { |
||||
|
// if (onTapCancel != null) onTapCancel(); |
||||
|
// if (onTapChange != null) onTapChange(false); |
||||
|
// }, |
||||
|
// onTap: () { |
||||
|
// if (onTap != null) onTap(); |
||||
|
// if (onTapChange != null) onTapChange(false); |
||||
|
// }, |
||||
|
// onTapUp: onTapUp, |
||||
|
// onDoubleTap: onDoubleTap, |
||||
|
// onLongPress: onLongPress, |
||||
|
// onLongPressStart: onLongPressStart, |
||||
|
// onLongPressEnd: onLongPressEnd, |
||||
|
// onLongPressMoveUpdate: onLongPressMoveUpdate, |
||||
|
// onLongPressUp: onLongPressUp, |
||||
|
// onVerticalDragStart: onVerticalDragStart, |
||||
|
// onVerticalDragEnd: onVerticalDragEnd, |
||||
|
// onVerticalDragDown: onVerticalDragDown, |
||||
|
// onVerticalDragCancel: onVerticalDragCancel, |
||||
|
// onVerticalDragUpdate: onVerticalDragUpdate, |
||||
|
// onHorizontalDragStart: onHorizontalDragStart, |
||||
|
// onHorizontalDragEnd: onHorizontalDragEnd, |
||||
|
// onHorizontalDragCancel: onHorizontalDragCancel, |
||||
|
// onHorizontalDragUpdate: onHorizontalDragUpdate, |
||||
|
// onHorizontalDragDown: onHorizontalDragDown, |
||||
|
// onForcePressStart: onForcePressStart, |
||||
|
// onForcePressEnd: onForcePressEnd, |
||||
|
// onForcePressPeak: onForcePressPeak, |
||||
|
// onForcePressUpdate: onForcePressUpdate, |
||||
|
// onPanStart: onPanStart, |
||||
|
// onPanEnd: onPanEnd, |
||||
|
// onPanCancel: onPanCancel, |
||||
|
// onPanDown: onPanDown, |
||||
|
// onPanUpdate: onPanUpdate, |
||||
|
// onScaleStart: onScaleStart, |
||||
|
// onScaleEnd: onScaleEnd, |
||||
|
// onScaleUpdate: onScaleUpdate, |
||||
|
// behavior: behavior ?? HitTestBehavior.opaque, |
||||
|
// excludeFromSemantics: excludeFromSemantics, |
||||
|
// dragStartBehavior: dragStartBehavior, |
||||
|
// child: this, |
||||
|
// ); |
||||
|
|
||||
|
/// 手势 |
||||
|
Widget onTap( |
||||
|
GestureTapCallback? onTap, { |
||||
|
Key? key, |
||||
|
HitTestBehavior? behavior, |
||||
|
bool excludeFromSemantics = false, |
||||
|
DragStartBehavior dragStartBehavior = DragStartBehavior.start, |
||||
|
}) => |
||||
|
GestureDetector( |
||||
|
key: key, |
||||
|
onTap: onTap, |
||||
|
behavior: behavior ?? HitTestBehavior.opaque, |
||||
|
excludeFromSemantics: excludeFromSemantics, |
||||
|
dragStartBehavior: dragStartBehavior, |
||||
|
child: this, |
||||
|
); |
||||
|
|
||||
|
/// 长按手势 |
||||
|
// Widget onLongPress( |
||||
|
// GestureTapCallback? onLongPress, { |
||||
|
// Key? key, |
||||
|
// HitTestBehavior? behavior, |
||||
|
// bool excludeFromSemantics = false, |
||||
|
// DragStartBehavior dragStartBehavior = DragStartBehavior.start, |
||||
|
// }) => |
||||
|
// GestureDetector( |
||||
|
// key: key, |
||||
|
// onLongPress: onLongPress, |
||||
|
// behavior: behavior ?? HitTestBehavior.opaque, |
||||
|
// excludeFromSemantics: excludeFromSemantics, |
||||
|
// dragStartBehavior: dragStartBehavior, |
||||
|
// child: this, |
||||
|
// ); |
||||
|
|
||||
|
/// 约束 高度 |
||||
|
Widget height( |
||||
|
double height, { |
||||
|
Key? key, |
||||
|
}) => |
||||
|
ConstrainedBox( |
||||
|
key: key, |
||||
|
constraints: BoxConstraints.tightFor(height: height), |
||||
|
child: this, |
||||
|
); |
||||
|
|
||||
|
/// 限制盒子 最大宽高 |
||||
|
// Widget limitedBox({ |
||||
|
// Key? key, |
||||
|
// double maxWidth = double.infinity, |
||||
|
// double maxHeight = double.infinity, |
||||
|
// }) => |
||||
|
// LimitedBox( |
||||
|
// key: key, |
||||
|
// maxWidth: maxWidth, |
||||
|
// maxHeight: maxHeight, |
||||
|
// child: this, |
||||
|
// ); |
||||
|
// |
||||
|
// /// 偏移 |
||||
|
// Widget offstage({ |
||||
|
// Key? key, |
||||
|
// bool offstage = true, |
||||
|
// }) => |
||||
|
// Offstage( |
||||
|
// key: key, |
||||
|
// offstage: offstage, |
||||
|
// child: this, |
||||
|
// ); |
||||
|
|
||||
|
/// 透明度 |
||||
|
Widget opacity( |
||||
|
double opacity, { |
||||
|
Key? key, |
||||
|
bool alwaysIncludeSemantics = false, |
||||
|
}) => |
||||
|
Opacity( |
||||
|
key: key, |
||||
|
opacity: opacity, |
||||
|
alwaysIncludeSemantics: alwaysIncludeSemantics, |
||||
|
child: this, |
||||
|
); |
||||
|
|
||||
|
/// 溢出 |
||||
|
Widget overflow({ |
||||
|
Key? key, |
||||
|
AlignmentGeometry alignment = Alignment.center, |
||||
|
double? minWidth, |
||||
|
double? maxWidth, |
||||
|
double? minHeight, |
||||
|
double? maxHeight, |
||||
|
}) => |
||||
|
OverflowBox( |
||||
|
key: key, |
||||
|
alignment: alignment, |
||||
|
minWidth: minWidth, |
||||
|
maxWidth: minWidth, |
||||
|
minHeight: minHeight, |
||||
|
maxHeight: maxHeight, |
||||
|
child: this, |
||||
|
); |
||||
|
|
||||
|
/// 内间距 |
||||
|
Widget padding({ |
||||
|
Key? key, |
||||
|
EdgeInsetsGeometry? value, |
||||
|
double? all, |
||||
|
double? horizontal, |
||||
|
double? vertical, |
||||
|
double? top, |
||||
|
double? bottom, |
||||
|
double? left, |
||||
|
double? right, |
||||
|
}) => |
||||
|
Padding( |
||||
|
key: key, |
||||
|
padding: value ?? |
||||
|
EdgeInsets.only( |
||||
|
top: top ?? vertical ?? all ?? 0.0, |
||||
|
bottom: bottom ?? vertical ?? all ?? 0.0, |
||||
|
left: left ?? horizontal ?? all ?? 0.0, |
||||
|
right: right ?? horizontal ?? all ?? 0.0, |
||||
|
), |
||||
|
child: this, |
||||
|
); |
||||
|
|
||||
|
// /// 内间距 全 |
||||
|
// Widget paddingAll(double val) => padding(all: val); |
||||
|
|
||||
|
/// 内间距 下 |
||||
|
Widget paddingBottom(double val) => padding(bottom: val); |
||||
|
|
||||
|
/// 内间距 横向 |
||||
|
Widget paddingHorizontal(double val) => padding(horizontal: val); |
||||
|
|
||||
|
/// 内间距 左 |
||||
|
Widget paddingLeft(double val) => padding(left: val); |
||||
|
|
||||
|
/// 内间距 右 |
||||
|
Widget paddingRight(double val) => padding(right: val); |
||||
|
|
||||
|
/// 内间距 上 |
||||
|
Widget paddingTop(double val) => padding(top: val); |
||||
|
|
||||
|
/// 内间距 纵向 |
||||
|
Widget paddingVertical(double val) => padding(vertical: val); |
||||
|
|
||||
|
/// 内间距 |
||||
|
Widget sliverPadding({ |
||||
|
Key? key, |
||||
|
EdgeInsetsGeometry? value, |
||||
|
double? all, |
||||
|
double? horizontal, |
||||
|
double? vertical, |
||||
|
double? top, |
||||
|
double? bottom, |
||||
|
double? left, |
||||
|
double? right, |
||||
|
}) => |
||||
|
SliverPadding( |
||||
|
key: key, |
||||
|
padding: value ?? |
||||
|
EdgeInsets.only( |
||||
|
top: top ?? vertical ?? all ?? 0.0, |
||||
|
bottom: bottom ?? vertical ?? all ?? 0.0, |
||||
|
left: left ?? horizontal ?? all ?? 0.0, |
||||
|
right: right ?? horizontal ?? all ?? 0.0, |
||||
|
), |
||||
|
sliver: this, |
||||
|
); |
||||
|
|
||||
|
// /// 内间距 下 |
||||
|
// Widget sliverPaddingBottom(double val) => sliverPadding(bottom: val); |
||||
|
// |
||||
|
// /// 内间距 横向 |
||||
|
// Widget sliverPaddingHorizontal(double val) => sliverPadding(horizontal: val); |
||||
|
// |
||||
|
// /// 内间距 左 |
||||
|
// Widget sliverPaddingLeft(double val) => sliverPadding(left: val); |
||||
|
// |
||||
|
// /// 内间距 右 |
||||
|
// Widget sliverPaddingRight(double val) => sliverPadding(right: val); |
||||
|
// |
||||
|
// /// 内间距 上 |
||||
|
// Widget sliverPaddingTop(double val) => sliverPadding(top: val); |
||||
|
// |
||||
|
// /// 内间距 纵向 |
||||
|
// Widget sliverPaddingVertical(double val) => sliverPadding(vertical: val); |
||||
|
|
||||
|
Widget marginAll(double margin) => |
||||
|
Container(margin: EdgeInsets.all(margin), child: this); |
||||
|
|
||||
|
Widget marginSymmetric({double horizontal = 0.0, double vertical = 0.0}) => |
||||
|
Container( |
||||
|
margin: |
||||
|
EdgeInsets.symmetric(horizontal: horizontal, vertical: vertical), |
||||
|
child: this); |
||||
|
|
||||
|
Widget marginOnly({ |
||||
|
double left = 0.0, |
||||
|
double top = 0.0, |
||||
|
double right = 0.0, |
||||
|
double bottom = 0.0, |
||||
|
}) => |
||||
|
Container( |
||||
|
margin: EdgeInsets.only( |
||||
|
top: top, left: left, right: right, bottom: bottom), |
||||
|
child: this); |
||||
|
|
||||
|
Widget get marginZero => Container(margin: EdgeInsets.zero, child: this); |
||||
|
|
||||
|
/// stack布局 位置 |
||||
|
// Widget positioned({ |
||||
|
// Key? key, |
||||
|
// double? left, |
||||
|
// double? top, |
||||
|
// double? right, |
||||
|
// double? bottom, |
||||
|
// double? width, |
||||
|
// double? height, |
||||
|
// }) => |
||||
|
// Positioned( |
||||
|
// key: key, |
||||
|
// left: left, |
||||
|
// top: top, |
||||
|
// right: right, |
||||
|
// bottom: bottom, |
||||
|
// width: width, |
||||
|
// height: height, |
||||
|
// child: this, |
||||
|
// ); |
||||
|
|
||||
|
// 墨水纹 |
||||
|
// Widget inkWell({ |
||||
|
// Key? key, |
||||
|
// Function()? onTap, |
||||
|
// double? borderRadius, |
||||
|
// }) => |
||||
|
// Material( |
||||
|
// color: Colors.transparent, |
||||
|
// child: Ink( |
||||
|
// child: InkWell( |
||||
|
// borderRadius: borderRadius != null |
||||
|
// ? BorderRadius.all( |
||||
|
// Radius.circular(borderRadius), |
||||
|
// ) |
||||
|
// : null, |
||||
|
// onTap: onTap ?? () {}, |
||||
|
// child: this, |
||||
|
// ), |
||||
|
// ), |
||||
|
// ); |
||||
|
|
||||
|
/// 涟漪 |
||||
|
// Widget ripple({ |
||||
|
// Key? key, |
||||
|
// Color? focusColor, |
||||
|
// Color? hoverColor, |
||||
|
// Color? highlightColor, |
||||
|
// Color? splashColor, |
||||
|
// InteractiveInkFeatureFactory? splashFactory, |
||||
|
// double? radius, |
||||
|
// ShapeBorder? customBorder, |
||||
|
// bool enableFeedback = true, |
||||
|
// bool excludeFromSemantics = false, |
||||
|
// FocusNode? focusNode, |
||||
|
// bool canRequestFocus = true, |
||||
|
// bool autoFocus = false, |
||||
|
// bool enable = true, |
||||
|
// }) => |
||||
|
// enable |
||||
|
// ? Builder( |
||||
|
// key: key, |
||||
|
// builder: (BuildContext context) { |
||||
|
// GestureDetector? gestures = |
||||
|
// context.findAncestorWidgetOfExactType<GestureDetector>(); |
||||
|
// return Material( |
||||
|
// color: Colors.transparent, |
||||
|
// child: InkWell( |
||||
|
// focusColor: focusColor, |
||||
|
// hoverColor: hoverColor, |
||||
|
// highlightColor: highlightColor, |
||||
|
// splashColor: splashColor, |
||||
|
// splashFactory: splashFactory, |
||||
|
// radius: radius, |
||||
|
// customBorder: customBorder, |
||||
|
// enableFeedback: enableFeedback, |
||||
|
// excludeFromSemantics: excludeFromSemantics, |
||||
|
// focusNode: focusNode, |
||||
|
// canRequestFocus: canRequestFocus, |
||||
|
// autofocus: autoFocus, |
||||
|
// onTap: gestures?.onTap, |
||||
|
// child: this, |
||||
|
// ), |
||||
|
// ); |
||||
|
// }, |
||||
|
// ) |
||||
|
// : Builder( |
||||
|
// key: key, |
||||
|
// builder: (context) => this, |
||||
|
// ); |
||||
|
|
||||
|
// 刘海屏 特殊屏幕 留白 |
||||
|
// Widget safeArea({ |
||||
|
// Key? key, |
||||
|
// bool top = true, |
||||
|
// bool bottom = true, |
||||
|
// bool left = true, |
||||
|
// bool right = true, |
||||
|
// }) => |
||||
|
// SafeArea( |
||||
|
// key: key, |
||||
|
// top: top, |
||||
|
// bottom: bottom, |
||||
|
// left: left, |
||||
|
// right: right, |
||||
|
// child: this, |
||||
|
// ); |
||||
|
|
||||
|
/// 比例缩放 |
||||
|
// Widget scale({ |
||||
|
// Key? key, |
||||
|
// double? all, |
||||
|
// double? x, |
||||
|
// double? y, |
||||
|
// Offset? origin, |
||||
|
// AlignmentGeometry alignment = Alignment.center, |
||||
|
// bool transformHitTests = true, |
||||
|
// }) => |
||||
|
// Transform( |
||||
|
// key: key, |
||||
|
// transform: Matrix4.diagonal3Values(x ?? all ?? 0, y ?? all ?? 0, 1.0), |
||||
|
// alignment: alignment, |
||||
|
// origin: origin, |
||||
|
// transformHitTests: transformHitTests, |
||||
|
// child: this, |
||||
|
// ); |
||||
|
|
||||
|
/// 滚动视图 |
||||
|
// Widget scrollable({ |
||||
|
// Key? key, |
||||
|
// Axis scrollDirection = Axis.vertical, |
||||
|
// bool reverse = false, |
||||
|
// bool? primary, |
||||
|
// ScrollPhysics? physics, |
||||
|
// ScrollController? controller, |
||||
|
// DragStartBehavior dragStartBehavior = DragStartBehavior.start, |
||||
|
// EdgeInsetsGeometry? padding, |
||||
|
// }) => |
||||
|
// SingleChildScrollView( |
||||
|
// key: key, |
||||
|
// scrollDirection: scrollDirection, |
||||
|
// reverse: reverse, |
||||
|
// primary: primary, |
||||
|
// physics: physics, |
||||
|
// controller: controller, |
||||
|
// dragStartBehavior: dragStartBehavior, |
||||
|
// padding: padding, |
||||
|
// child: this, |
||||
|
// ); |
||||
|
|
||||
|
/// 语义调试 |
||||
|
/// MaterialApp.showSemanticsDebugger: true, |
||||
|
// Widget semanticsLabel( |
||||
|
// String label, { |
||||
|
// Key? key, |
||||
|
// }) => |
||||
|
// Semantics.fromProperties( |
||||
|
// key: key, |
||||
|
// properties: SemanticsProperties(label: label), |
||||
|
// child: this, |
||||
|
// ); |
||||
|
|
||||
|
/// 约束 宽高 |
||||
|
// Widget tight({ |
||||
|
// double? width, |
||||
|
// double? height, |
||||
|
// Key? key, |
||||
|
// }) => |
||||
|
// ConstrainedBox( |
||||
|
// key: key, |
||||
|
// constraints: BoxConstraints.tightFor(width: width, height: height), |
||||
|
// child: this, |
||||
|
// ); |
||||
|
|
||||
|
/// 约束 宽高 size |
||||
|
// Widget tightSize( |
||||
|
// double size, { |
||||
|
// Key? key, |
||||
|
// }) => |
||||
|
// ConstrainedBox( |
||||
|
// key: key, |
||||
|
// constraints: BoxConstraints.tightFor(width: size, height: size), |
||||
|
// child: this, |
||||
|
// ); |
||||
|
|
||||
|
/// transforms Matrix4 |
||||
|
// Widget transform({ |
||||
|
// Key? key, |
||||
|
// required Matrix4 transform, |
||||
|
// Offset? origin, |
||||
|
// AlignmentGeometry? alignment, |
||||
|
// bool transformHitTests = true, |
||||
|
// }) => |
||||
|
// Transform( |
||||
|
// key: key, |
||||
|
// transform: transform, |
||||
|
// alignment: alignment, |
||||
|
// origin: origin, |
||||
|
// transformHitTests: transformHitTests, |
||||
|
// child: this, |
||||
|
// ); |
||||
|
|
||||
|
/// translate 变化位置 |
||||
|
// Widget translate({ |
||||
|
// Key? key, |
||||
|
// required Offset offset, |
||||
|
// bool transformHitTests = true, |
||||
|
// }) => |
||||
|
// Transform.translate( |
||||
|
// key: key, |
||||
|
// offset: offset, |
||||
|
// transformHitTests: transformHitTests, |
||||
|
// child: this, |
||||
|
// ); |
||||
|
|
||||
|
/// 约束 宽度 |
||||
|
Widget width( |
||||
|
double width, { |
||||
|
Key? key, |
||||
|
}) => |
||||
|
ConstrainedBox( |
||||
|
key: key, |
||||
|
constraints: BoxConstraints.tightFor(width: width), |
||||
|
child: this, |
||||
|
); |
||||
|
|
||||
|
/// SliverToBoxAdapter |
||||
|
// Widget sliverToBoxAdapter({ |
||||
|
// Key? key, |
||||
|
// }) => |
||||
|
// SliverToBoxAdapter(key: key, child: this); |
||||
|
} |
||||
|
|
||||
|
class HabiPreventFastClick { |
||||
|
static const Duration _duration = Duration(seconds: 1); |
||||
|
static DateTime? _lastTime; |
||||
|
|
||||
|
static bool canClick(BuildContext context) { |
||||
|
final now = DateTime.now(); |
||||
|
if (_lastTime != null && now.difference(_lastTime!) < _duration) { |
||||
|
// 如果上次点击的时间距离现在不超过1秒,则不允许点击 |
||||
|
return false; |
||||
|
} |
||||
|
// 更新最后点击时间 |
||||
|
_lastTime = now; |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
import 'package:flutter/material.dart'; |
||||
|
|
||||
|
class DiscoverPage extends StatefulWidget { |
||||
|
const DiscoverPage({super.key}); |
||||
|
|
||||
|
@override |
||||
|
State<DiscoverPage> createState() => _DiscoverPageState(); |
||||
|
} |
||||
|
|
||||
|
class _DiscoverPageState extends State<DiscoverPage> with AutomaticKeepAliveClientMixin{ |
||||
|
@override |
||||
|
Widget build(BuildContext context) { |
||||
|
super.build(context); |
||||
|
return Container( |
||||
|
color: Colors.white, |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
bool get wantKeepAlive => true; |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
import 'package:flutter/cupertino.dart'; |
||||
|
import 'package:flutter/material.dart'; |
||||
|
|
||||
|
class HomePage extends StatefulWidget { |
||||
|
const HomePage({super.key}); |
||||
|
|
||||
|
@override |
||||
|
State<HomePage> createState() => _HomePageState(); |
||||
|
} |
||||
|
|
||||
|
class _HomePageState extends State<HomePage> with AutomaticKeepAliveClientMixin{ |
||||
|
|
||||
|
@override |
||||
|
Widget build(BuildContext context) { |
||||
|
super.build(context); |
||||
|
return Container( |
||||
|
color: Colors.white, |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
// TODO: implement wantKeepAlive |
||||
|
bool get wantKeepAlive => true; |
||||
|
} |
||||
@ -0,0 +1,91 @@ |
|||||
|
import 'package:dating_touchme_app/pages/main/tabbar/main_tab_bar.dart'; |
||||
|
import 'package:dating_touchme_app/pages/message/message_page.dart'; |
||||
|
import 'package:dating_touchme_app/pages/mine/mine_page.dart'; |
||||
|
import 'package:flutter/material.dart'; |
||||
|
import 'package:get/get.dart'; |
||||
|
import 'package:get_storage/get_storage.dart'; |
||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; |
||||
|
import 'package:dating_touchme_app/network/user_api.dart'; |
||||
|
|
||||
|
import '../../widget/double_tap_to_exit_widget.dart'; |
||||
|
import '../discover/discover_page.dart'; |
||||
|
import '../home/home_page.dart'; |
||||
|
// 移除未使用的导入 |
||||
|
|
||||
|
class MainPage extends StatefulWidget { |
||||
|
const MainPage({super.key}); |
||||
|
|
||||
|
@override |
||||
|
State<MainPage> createState() => _MainPageState(); |
||||
|
} |
||||
|
|
||||
|
class _MainPageState extends State<MainPage> { |
||||
|
late UserApi _userApi; |
||||
|
final storage = GetStorage(); |
||||
|
PageController pageController = PageController(initialPage: 0); |
||||
|
int currentIndex = 0; // 使用普通int替代RxInt |
||||
|
|
||||
|
// 将页面实例存储为成员变量,避免每次build都重新创建 |
||||
|
late HomePage homePage; |
||||
|
late DiscoverPage discoverPage; |
||||
|
late MessagePage messagePage; |
||||
|
late MinePage minePage; |
||||
|
|
||||
|
@override |
||||
|
void initState() { |
||||
|
super.initState(); |
||||
|
// 获取UserApi实例 |
||||
|
_userApi = Get.find<UserApi>(); |
||||
|
|
||||
|
// 初始化页面实例 |
||||
|
homePage = HomePage(); |
||||
|
discoverPage = DiscoverPage(); |
||||
|
messagePage = MessagePage(); |
||||
|
minePage = MinePage(); |
||||
|
|
||||
|
// 检查token并调用获取婚姻信息详情的方法 |
||||
|
checkTokenAndFetchMarriageInfo(); |
||||
|
} |
||||
|
|
||||
|
// 检查token并获取婚姻信息详情 |
||||
|
Future<void> checkTokenAndFetchMarriageInfo() async { |
||||
|
final response = await _userApi.getMarriageInformationDetail(); |
||||
|
if (response.data.isSuccess) { |
||||
|
if (response.data.data == null) { |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
Widget build(BuildContext context) { |
||||
|
return DoubleTapToExitWidget( |
||||
|
child: Scaffold( |
||||
|
backgroundColor: Colors.transparent, |
||||
|
resizeToAvoidBottomInset: false, |
||||
|
body: Stack( |
||||
|
alignment: Alignment.bottomCenter, |
||||
|
children: [ |
||||
|
PageView( |
||||
|
physics: const NeverScrollableScrollPhysics(), |
||||
|
controller: pageController, |
||||
|
children: [ |
||||
|
homePage, // 使用成员变量引用 |
||||
|
discoverPage, |
||||
|
messagePage, |
||||
|
minePage, |
||||
|
], |
||||
|
), |
||||
|
MainTabBar( |
||||
|
initialIndex: currentIndex, |
||||
|
onTabChanged: (index) { |
||||
|
currentIndex = index; |
||||
|
pageController.jumpToPage(index); |
||||
|
}, |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,182 @@ |
|||||
|
import 'dart:io'; |
||||
|
import 'package:collection/collection.dart'; |
||||
|
import 'package:dating_touchme_app/extension/ex_context.dart'; |
||||
|
import 'package:flutter/material.dart'; |
||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart'; |
||||
|
|
||||
|
import '../../../generated/assets.dart'; |
||||
|
import 'main_tab_btn.dart'; |
||||
|
import 'main_tab_item.dart'; |
||||
|
|
||||
|
class MainTabBar extends StatefulWidget { |
||||
|
const MainTabBar({super.key, this.initialIndex = 0, this.onTabChanged}); |
||||
|
|
||||
|
final int initialIndex; |
||||
|
final void Function(int index)? onTabChanged; |
||||
|
|
||||
|
@override |
||||
|
State<MainTabBar> createState() => _MainTabBarState(); |
||||
|
} |
||||
|
|
||||
|
class _MainTabBarState extends State<MainTabBar> { |
||||
|
int _selecteIndex = 0; |
||||
|
|
||||
|
final List<MainTabItem> items = const [ |
||||
|
MainTabItem( |
||||
|
title: "首页", |
||||
|
icon: Assets.imagesHomeNol, |
||||
|
selectedIcon: Assets.imagesHomePre, |
||||
|
), |
||||
|
MainTabItem( |
||||
|
title: "找对象", |
||||
|
icon: Assets.imagesDiscoverNol, |
||||
|
selectedIcon: Assets.imagesDiscoverPre, |
||||
|
), |
||||
|
MainTabItem( |
||||
|
title: "消息", |
||||
|
icon: Assets.imagesMessageNol, |
||||
|
selectedIcon: Assets.imagesMessagePre, |
||||
|
), |
||||
|
MainTabItem( |
||||
|
title: "我的", |
||||
|
icon: Assets.imagesMineNol, |
||||
|
selectedIcon: Assets.imagesMinePre, |
||||
|
), |
||||
|
]; |
||||
|
|
||||
|
@override |
||||
|
void initState() { |
||||
|
_selecteIndex = widget.initialIndex; |
||||
|
super.initState(); |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
void didUpdateWidget(covariant MainTabBar oldWidget) { |
||||
|
super.didUpdateWidget(oldWidget); |
||||
|
if (_selecteIndex != widget.initialIndex) { |
||||
|
_selecteIndex = widget.initialIndex; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
Widget build(BuildContext context) { |
||||
|
return Builder(builder: (context) { |
||||
|
if (Platform.isIOS) { |
||||
|
if (context.bottomPadding > 0) { |
||||
|
return Container( |
||||
|
height: 64.w, |
||||
|
color: Colors.white, |
||||
|
child: Stack( |
||||
|
children: [ |
||||
|
Column( |
||||
|
mainAxisAlignment: MainAxisAlignment.center, |
||||
|
children: [ |
||||
|
Row( |
||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
||||
|
children: items.mapIndexed((index, item) { |
||||
|
return MainTabButton( |
||||
|
selected: index == _selecteIndex, |
||||
|
isRedDot: item.showRedDot, |
||||
|
title: item.title, |
||||
|
onTap: () { |
||||
|
if (_selecteIndex != index) { |
||||
|
_selecteIndex = index; |
||||
|
setState(() {}); |
||||
|
widget.onTabChanged?.call(index); |
||||
|
} |
||||
|
}, |
||||
|
icon: item.icon, |
||||
|
selectedIcon: item.selectedIcon, |
||||
|
); |
||||
|
}).toList()), |
||||
|
SizedBox( |
||||
|
height: 22.w, |
||||
|
) |
||||
|
], |
||||
|
) |
||||
|
], |
||||
|
), |
||||
|
); |
||||
|
} else { |
||||
|
return SafeArea( |
||||
|
top: false, |
||||
|
child: Container( |
||||
|
height: 64.w, |
||||
|
color: Colors.white, |
||||
|
child: Stack( |
||||
|
children: [ |
||||
|
Column( |
||||
|
mainAxisAlignment: MainAxisAlignment.center, |
||||
|
children: [ |
||||
|
Row( |
||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
||||
|
children: items.mapIndexed((index, item) { |
||||
|
return MainTabButton( |
||||
|
selected: index == _selecteIndex, |
||||
|
isRedDot: item.showRedDot, |
||||
|
title: item.title, |
||||
|
onTap: () { |
||||
|
if (_selecteIndex != index) { |
||||
|
_selecteIndex = index; |
||||
|
setState(() {}); |
||||
|
widget.onTabChanged?.call(index); |
||||
|
} |
||||
|
}, |
||||
|
icon: item.icon, |
||||
|
selectedIcon: item.selectedIcon, |
||||
|
); |
||||
|
}).toList()), |
||||
|
SizedBox( |
||||
|
height: 14.w, |
||||
|
) |
||||
|
], |
||||
|
) |
||||
|
], |
||||
|
), |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
} else { |
||||
|
return SafeArea( |
||||
|
top: false, |
||||
|
child: Container( |
||||
|
// padding: EdgeInsets.only(bottom: 14.w), |
||||
|
// margin: EdgeInsets.only(left: 16.w, right: 16.w, bottom: 14.w), |
||||
|
height: 64.w, |
||||
|
color: Colors.white, |
||||
|
child: Stack( |
||||
|
children: [ |
||||
|
Column( |
||||
|
mainAxisAlignment: MainAxisAlignment.center, |
||||
|
children: [ |
||||
|
Row( |
||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
||||
|
children: items.mapIndexed((index, item) { |
||||
|
return MainTabButton( |
||||
|
selected: index == _selecteIndex, |
||||
|
isRedDot: item.showRedDot, |
||||
|
title: item.title, |
||||
|
onTap: () { |
||||
|
if (_selecteIndex != index) { |
||||
|
_selecteIndex = index; |
||||
|
setState(() {}); |
||||
|
widget.onTabChanged?.call(index); |
||||
|
} |
||||
|
}, |
||||
|
icon: item.icon, |
||||
|
selectedIcon: item.selectedIcon, |
||||
|
); |
||||
|
}).toList()), |
||||
|
SizedBox( |
||||
|
height: 14.w, |
||||
|
) |
||||
|
], |
||||
|
) |
||||
|
], |
||||
|
), |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,77 @@ |
|||||
|
import 'package:flutter/material.dart'; |
||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart'; |
||||
|
|
||||
|
class MainTabButton extends StatefulWidget { |
||||
|
final VoidCallback? onTap; |
||||
|
final bool isRedDot; |
||||
|
final bool selected; |
||||
|
final String icon; |
||||
|
final String title; |
||||
|
final String selectedIcon; |
||||
|
|
||||
|
const MainTabButton({ |
||||
|
super.key, |
||||
|
required this.selected, |
||||
|
required this.icon, |
||||
|
required this.selectedIcon, |
||||
|
this.isRedDot = false, |
||||
|
this.onTap, |
||||
|
required this.title |
||||
|
}); |
||||
|
|
||||
|
@override |
||||
|
State<MainTabButton> createState() => MainTabButtonState(); |
||||
|
} |
||||
|
|
||||
|
class MainTabButtonState extends State<MainTabButton> { |
||||
|
@override |
||||
|
Widget build(BuildContext context) { |
||||
|
if (widget.isRedDot) { |
||||
|
return Expanded( |
||||
|
child: GestureDetector( |
||||
|
onTap: widget.onTap, |
||||
|
child: Container( |
||||
|
color: Colors.transparent, |
||||
|
alignment: Alignment.center, |
||||
|
child: Stack( |
||||
|
clipBehavior: Clip.none, |
||||
|
children: [ |
||||
|
Column( |
||||
|
children: [ |
||||
|
SizedBox(height: 5.w,), |
||||
|
widget.selected |
||||
|
? Image.asset(widget.selectedIcon, width: 25.w, height: 25.w) |
||||
|
: Image.asset(widget.icon, width: 25.w, height: 25.w), |
||||
|
Text(widget.title, style: const TextStyle( |
||||
|
fontSize: 12 |
||||
|
),) |
||||
|
], |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
), |
||||
|
)); |
||||
|
} |
||||
|
|
||||
|
return Expanded( |
||||
|
child: GestureDetector( |
||||
|
onTap: widget.onTap, |
||||
|
child: Container( |
||||
|
alignment: Alignment.center, |
||||
|
color: Colors.transparent, |
||||
|
child: Column( |
||||
|
children: [ |
||||
|
SizedBox(height: 5.w,), |
||||
|
widget.selected |
||||
|
? Image.asset(widget.selectedIcon, width: 25.w, height: 25.w) |
||||
|
: Image.asset(widget.icon, width: 25.w, height: 25.w), |
||||
|
Text(widget.title, style: const TextStyle( |
||||
|
fontSize: 12 |
||||
|
),) |
||||
|
], |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
class MainTabItem { |
||||
|
final String title; |
||||
|
final String icon; |
||||
|
final String selectedIcon; |
||||
|
final bool showRedDot; |
||||
|
|
||||
|
const MainTabItem({ |
||||
|
required this.title, |
||||
|
required this.icon, |
||||
|
required this.selectedIcon, |
||||
|
this.showRedDot = false, |
||||
|
}); |
||||
|
} |
||||
@ -1,45 +0,0 @@ |
|||||
import 'package:flutter/material.dart'; |
|
||||
import 'package:get/get.dart'; |
|
||||
import 'package:get_storage/get_storage.dart'; |
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; |
|
||||
import 'package:dating_touchme_app/network/user_api.dart'; |
|
||||
// 移除未使用的导入 |
|
||||
|
|
||||
class MainPage extends StatefulWidget { |
|
||||
const MainPage({super.key}); |
|
||||
|
|
||||
@override |
|
||||
State<MainPage> createState() => _MainPageState(); |
|
||||
} |
|
||||
|
|
||||
class _MainPageState extends State<MainPage> { |
|
||||
late UserApi _userApi; |
|
||||
final storage = GetStorage(); |
|
||||
|
|
||||
@override |
|
||||
void initState() { |
|
||||
super.initState(); |
|
||||
// 获取UserApi实例 |
|
||||
_userApi = Get.find<UserApi>(); |
|
||||
|
|
||||
// 检查token并调用获取婚姻信息详情的方法 |
|
||||
checkTokenAndFetchMarriageInfo(); |
|
||||
} |
|
||||
|
|
||||
// 检查token并获取婚姻信息详情 |
|
||||
Future<void> checkTokenAndFetchMarriageInfo() async { |
|
||||
final response = await _userApi.getMarriageInformationDetail(); |
|
||||
if (response.data.isSuccess) { |
|
||||
if (response.data.data == null) { |
|
||||
// 跳转到完善信息页面 |
|
||||
SmartDialog.showToast('跳转到完善信息'); |
|
||||
// 这里可以添加跳转到完善信息页面的逻辑 |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@override |
|
||||
Widget build(BuildContext context) { |
|
||||
return const Placeholder(); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,22 @@ |
|||||
|
import 'package:flutter/material.dart'; |
||||
|
|
||||
|
class MessagePage extends StatefulWidget { |
||||
|
const MessagePage({super.key}); |
||||
|
|
||||
|
@override |
||||
|
State<MessagePage> createState() => _MessagePageState(); |
||||
|
} |
||||
|
|
||||
|
class _MessagePageState extends State<MessagePage> with AutomaticKeepAliveClientMixin{ |
||||
|
@override |
||||
|
Widget build(BuildContext context) { |
||||
|
super.build(context); |
||||
|
return Container( |
||||
|
color: Colors.white, |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
// TODO: implement wantKeepAlive |
||||
|
bool get wantKeepAlive => true; |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
import 'package:flutter/material.dart'; |
||||
|
|
||||
|
class MinePage extends StatefulWidget { |
||||
|
const MinePage({super.key}); |
||||
|
|
||||
|
@override |
||||
|
State<MinePage> createState() => _MinePageState(); |
||||
|
} |
||||
|
|
||||
|
class _MinePageState extends State<MinePage> with AutomaticKeepAliveClientMixin{ |
||||
|
@override |
||||
|
Widget build(BuildContext context) { |
||||
|
return Container( |
||||
|
color: Colors.white, |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
// TODO: implement wantKeepAlive |
||||
|
bool get wantKeepAlive => true; |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
import 'package:flutter/material.dart'; |
||||
|
import 'package:flutter/services.dart'; |
||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; |
||||
|
|
||||
|
class DoubleTapToExitWidget extends StatefulWidget { |
||||
|
final Widget child; |
||||
|
final Duration duration; |
||||
|
|
||||
|
const DoubleTapToExitWidget( |
||||
|
{super.key, |
||||
|
required this.child, |
||||
|
this.duration = const Duration(seconds: 1)}); |
||||
|
|
||||
|
@override |
||||
|
State<DoubleTapToExitWidget> createState() => DoubleTapToExitWidgetState(); |
||||
|
} |
||||
|
|
||||
|
class DoubleTapToExitWidgetState extends State<DoubleTapToExitWidget> { |
||||
|
DateTime? lastPressedAt; |
||||
|
|
||||
|
@override |
||||
|
Widget build(BuildContext context) { |
||||
|
return PopScope( |
||||
|
canPop: false, |
||||
|
onPopInvokedWithResult: (bool didPop, Object? result) async { |
||||
|
final now = DateTime.now(); |
||||
|
if (lastPressedAt == null || |
||||
|
now.difference(lastPressedAt!) >= widget.duration) { |
||||
|
setState(() { |
||||
|
lastPressedAt = now; |
||||
|
}); |
||||
|
SmartDialog.showToast('再按一次退出'); |
||||
|
} else { |
||||
|
SystemNavigator.pop(); |
||||
|
} |
||||
|
}, |
||||
|
child: widget.child); |
||||
|
} |
||||
|
} |
||||
308
pubspec.lock
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
Write
Preview
Loading…
Cancel
Save