feat: 整体常用控件封装,常用工具类。首页数据对接
This commit is contained in:
1 parent
d90d023e85
commit
fbcd33ea2b
50 files changed
+4558
-264
No files matched your search
@@ -1,6 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
||||
// 扩展Widget类以添加额外的布局和样式方法
|
||||
extension WidgetExtend on Widget {
|
||||
/// 在当前Widget外层添加一个Container
|
||||
@@ -43,7 +42,8 @@ extension WidgetExtend on Widget {
|
||||
padding: padding,
|
||||
alignment: alignment,
|
||||
clipBehavior: clipBehavior,
|
||||
constraints: constraints ??
|
||||
constraints:
|
||||
constraints ??
|
||||
BoxConstraints(
|
||||
minWidth: minWidth ?? 0.0,
|
||||
maxWidth: maxWidth ?? double.infinity,
|
||||
@@ -59,8 +59,9 @@ extension WidgetExtend on Widget {
|
||||
image: image,
|
||||
gradient: gradient,
|
||||
border: border,
|
||||
borderRadius:
|
||||
radius != null ? BorderRadius.circular(radius) : borderRadius,
|
||||
borderRadius: radius != null
|
||||
? BorderRadius.circular(radius)
|
||||
: borderRadius,
|
||||
),
|
||||
child: this,
|
||||
).addInkWell(onTap: onTap);
|
||||
@@ -84,10 +85,7 @@ extension WidgetExtend on Widget {
|
||||
///
|
||||
/// 通过此方法可以设置控件的对齐方式
|
||||
Widget addAlign({AlignmentGeometry alignment = Alignment.centerLeft}) {
|
||||
return Align(
|
||||
alignment: alignment,
|
||||
child: this,
|
||||
);
|
||||
return Align(alignment: alignment, child: this);
|
||||
}
|
||||
|
||||
Widget addCenter() {
|
||||
@@ -118,20 +116,14 @@ extension WidgetExtend on Widget {
|
||||
///
|
||||
/// 通过此方法可以控制控件的显示和隐藏
|
||||
Widget addVisibility({bool visible = true}) {
|
||||
return Visibility(
|
||||
visible: visible,
|
||||
child: this,
|
||||
);
|
||||
return Visibility(visible: visible, child: this);
|
||||
}
|
||||
|
||||
/// 在当前Widget外层添加一个Transform.rotate
|
||||
///
|
||||
/// 通过此方法可以设置控件的旋转角度
|
||||
Widget addRotate({double angle = 0}) {
|
||||
return Transform.rotate(
|
||||
angle: angle,
|
||||
child: this,
|
||||
);
|
||||
return Transform.rotate(angle: angle, child: this);
|
||||
}
|
||||
|
||||
/// 在当前Widget外层添加一个InkWell
|
||||
@@ -141,44 +133,27 @@ extension WidgetExtend on Widget {
|
||||
if (onTap == null) {
|
||||
return this;
|
||||
}
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
child: this,
|
||||
);
|
||||
return InkWell(onTap: onTap, child: this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// 在当前Widget外层添加一个SizedBox
|
||||
///
|
||||
/// 通过此方法可以设置控件的宽高
|
||||
Widget addSize({double? width, double? height}) {
|
||||
return SizedBox(
|
||||
width: width,
|
||||
height: height,
|
||||
child: this,
|
||||
);
|
||||
return SizedBox(width: width, height: height, child: this);
|
||||
}
|
||||
|
||||
/// 在当前Widget外层添加一个Opacity
|
||||
///
|
||||
/// 通过此方法可以设置控件的透明度
|
||||
Widget addOpacity({double opacity = 1}) {
|
||||
return Opacity(
|
||||
opacity: opacity,
|
||||
child: this,
|
||||
);
|
||||
return Opacity(opacity: opacity, child: this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
extension OnRenderObjectWidget on RenderObjectWidget {
|
||||
SliverPadding addPaddingSliver({EdgeInsets? padding}) {
|
||||
return SliverPadding(
|
||||
padding: padding ?? EdgeInsets.zero,
|
||||
sliver: this,
|
||||
);
|
||||
return SliverPadding(padding: padding ?? EdgeInsets.zero, sliver: this);
|
||||
}
|
||||
|
||||
DecoratedSliver addDecoratedSliver({
|
||||
@@ -199,7 +174,7 @@ extension OnRenderObjectWidget on RenderObjectWidget {
|
||||
gradient: gradient,
|
||||
shape: shape ?? BoxShape.rectangle,
|
||||
),
|
||||
sliver: this.addPaddingSliver(padding: padding),
|
||||
sliver: addPaddingSliver(padding: padding),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import '../model_parser.dart';
|
||||
|
||||
class SkywoodCategory {
|
||||
final int id;
|
||||
final String name;
|
||||
|
||||
const SkywoodCategory({required this.id, required this.name});
|
||||
|
||||
factory SkywoodCategory.fromJson(Map<String, dynamic> json) {
|
||||
return SkywoodCategory(
|
||||
id: parseInt(json['id']),
|
||||
name: parseString(json['name']),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {'id': id, 'name': name};
|
||||
}
|
||||
|
||||
SkywoodCategory copyWith({int? id, String? name}) {
|
||||
return SkywoodCategory(id: id ?? this.id, name: name ?? this.name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import '../model_parser.dart';
|
||||
import 'skywood_drama.dart';
|
||||
import 'skywood_episode.dart';
|
||||
|
||||
class SkywoodDetail {
|
||||
final SkywoodDrama? videoInfo;
|
||||
final SkywoodDrama? shortPlayInfo;
|
||||
final List<SkywoodEpisode> episodeList;
|
||||
final String? businessModel;
|
||||
bool? isCollect;
|
||||
final bool? showShareCoin;
|
||||
final int? shareCoin;
|
||||
final int? revolution;
|
||||
final String? userLevel;
|
||||
final int? jumpType;
|
||||
final int? jumpShortPlayId;
|
||||
final List<int>? checkPoint;
|
||||
|
||||
SkywoodDetail({
|
||||
required this.videoInfo,
|
||||
this.shortPlayInfo,
|
||||
required this.episodeList,
|
||||
this.businessModel,
|
||||
this.isCollect,
|
||||
this.showShareCoin,
|
||||
this.shareCoin,
|
||||
this.revolution,
|
||||
this.userLevel,
|
||||
this.jumpType,
|
||||
this.jumpShortPlayId,
|
||||
this.checkPoint,
|
||||
});
|
||||
|
||||
factory SkywoodDetail.fromJson(Map<String, dynamic> json) {
|
||||
return SkywoodDetail(
|
||||
videoInfo: parseMap(json['video_info']) == null
|
||||
? null
|
||||
: SkywoodDrama.fromJson(parseMap(json['video_info'])!),
|
||||
shortPlayInfo: parseMap(json['shortPlayInfo']) == null
|
||||
? null
|
||||
: SkywoodDrama.fromJson(parseMap(json['shortPlayInfo'])!),
|
||||
episodeList: parseModelList(json['episodeList'], SkywoodEpisode.fromJson),
|
||||
businessModel: json['business_model'] as String?,
|
||||
isCollect: json['is_collect'] == null
|
||||
? null
|
||||
: parseBool(json['is_collect']),
|
||||
showShareCoin: json['show_share_coin'] == null
|
||||
? null
|
||||
: parseBool(json['show_share_coin']),
|
||||
shareCoin: json['share_coin'] == null
|
||||
? null
|
||||
: parseInt(json['share_coin']),
|
||||
revolution: json['revolution'] == null
|
||||
? null
|
||||
: parseInt(json['revolution']),
|
||||
userLevel: json['user_level'] as String?,
|
||||
jumpType: json['jump_type'] == null ? null : parseInt(json['jump_type']),
|
||||
jumpShortPlayId: json['jump_short_play_id'] == null
|
||||
? null
|
||||
: parseInt(json['jump_short_play_id']),
|
||||
checkPoint: parseIntList(json['check_point']),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'video_info': videoInfo?.toJson(),
|
||||
'shortPlayInfo': shortPlayInfo?.toJson(),
|
||||
'episodeList': episodeList.map((item) => item.toJson()).toList(),
|
||||
'business_model': businessModel,
|
||||
'is_collect': isCollect,
|
||||
'show_share_coin': showShareCoin,
|
||||
'share_coin': shareCoin,
|
||||
'revolution': revolution,
|
||||
'user_level': userLevel,
|
||||
'jump_type': jumpType,
|
||||
'jump_short_play_id': jumpShortPlayId,
|
||||
'check_point': checkPoint,
|
||||
};
|
||||
}
|
||||
|
||||
SkywoodDetail copyWith({
|
||||
SkywoodDrama? videoInfo,
|
||||
SkywoodDrama? shortPlayInfo,
|
||||
List<SkywoodEpisode>? episodeList,
|
||||
String? businessModel,
|
||||
bool? isCollect,
|
||||
bool? showShareCoin,
|
||||
int? shareCoin,
|
||||
int? revolution,
|
||||
String? userLevel,
|
||||
int? jumpType,
|
||||
int? jumpShortPlayId,
|
||||
List<int>? checkPoint,
|
||||
}) {
|
||||
return SkywoodDetail(
|
||||
videoInfo: videoInfo ?? this.videoInfo,
|
||||
shortPlayInfo: shortPlayInfo ?? this.shortPlayInfo,
|
||||
episodeList: episodeList ?? this.episodeList,
|
||||
businessModel: businessModel ?? this.businessModel,
|
||||
isCollect: isCollect ?? this.isCollect,
|
||||
showShareCoin: showShareCoin ?? this.showShareCoin,
|
||||
shareCoin: shareCoin ?? this.shareCoin,
|
||||
revolution: revolution ?? this.revolution,
|
||||
userLevel: userLevel ?? this.userLevel,
|
||||
jumpType: jumpType ?? this.jumpType,
|
||||
jumpShortPlayId: jumpShortPlayId ?? this.jumpShortPlayId,
|
||||
checkPoint: checkPoint ?? this.checkPoint,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
import '../model_parser.dart';
|
||||
import 'skywood_category.dart';
|
||||
|
||||
class SkywoodDrama {
|
||||
final int? id;
|
||||
final int shortId;
|
||||
int? shortPlayId;
|
||||
final String name;
|
||||
final String description;
|
||||
final String imageUrl;
|
||||
final String horizontallyImg;
|
||||
int collectTotal;
|
||||
final int watchTotal;
|
||||
final int episodeTotal;
|
||||
final String? playSeconds;
|
||||
bool? isCollect;
|
||||
final String? videoUrl;
|
||||
final Map<String, dynamic>? videoInfo;
|
||||
final List<String>? category;
|
||||
final List<SkywoodCategory>? categoryList;
|
||||
final String tagType;
|
||||
int? episode;
|
||||
final int? process;
|
||||
final int? buyType;
|
||||
final int? searchClickTotal;
|
||||
final int? shortPlayVideoId;
|
||||
final int? currentEpisode;
|
||||
int? historyTime;
|
||||
|
||||
|
||||
SkywoodDrama({
|
||||
this.id,
|
||||
required this.shortId,
|
||||
this.shortPlayId,
|
||||
required this.name,
|
||||
required this.description,
|
||||
required this.imageUrl,
|
||||
required this.horizontallyImg,
|
||||
required this.collectTotal,
|
||||
required this.watchTotal,
|
||||
required this.episodeTotal,
|
||||
this.playSeconds,
|
||||
this.isCollect,
|
||||
this.videoUrl,
|
||||
this.videoInfo,
|
||||
this.category,
|
||||
this.categoryList,
|
||||
required this.tagType,
|
||||
this.episode,
|
||||
this.process,
|
||||
this.buyType,
|
||||
this.searchClickTotal,
|
||||
this.shortPlayVideoId,
|
||||
this.currentEpisode,
|
||||
this.historyTime,
|
||||
});
|
||||
|
||||
factory SkywoodDrama.fromJson(Map<String, dynamic> json) {
|
||||
return SkywoodDrama(
|
||||
id: json['id'] == null ? null : parseInt(json['id']),
|
||||
shortId: parseInt(json['short_id']),
|
||||
shortPlayId: json['short_play_id'] == null
|
||||
? null
|
||||
: parseInt(json['short_play_id']),
|
||||
name: parseString(json['name']),
|
||||
description: parseString(json['description']),
|
||||
imageUrl: parseString(json['image_url']),
|
||||
horizontallyImg: parseString(json['horizontally_img']),
|
||||
collectTotal: parseInt(json['collect_total']),
|
||||
watchTotal: parseInt(json['watch_total']),
|
||||
episodeTotal: parseInt(json['episode_total']),
|
||||
playSeconds: json['play_seconds'] as String?,
|
||||
isCollect: json['is_collect'] == null
|
||||
? null
|
||||
: parseBool(json['is_collect']),
|
||||
videoUrl: json['video_url'] as String?,
|
||||
videoInfo: parseMap(json['video_info']),
|
||||
category: parseStringList(json['category']),
|
||||
categoryList: json['categoryList'] == null
|
||||
? null
|
||||
: parseModelList(json['categoryList'], SkywoodCategory.fromJson),
|
||||
tagType: parseString(json['tag_type']),
|
||||
episode: json['episode'] == null ? null : parseInt(json['episode']),
|
||||
process: json['process'] == null ? null : parseInt(json['process']),
|
||||
buyType: json['buy_type'] == null ? null : parseInt(json['buy_type']),
|
||||
searchClickTotal: json['search_click_total'] == null
|
||||
? null
|
||||
: parseInt(json['search_click_total']),
|
||||
shortPlayVideoId: json['short_play_video_id'] == null
|
||||
? null
|
||||
: parseInt(json['short_play_video_id']),
|
||||
currentEpisode: json['current_episode'] == null
|
||||
? null
|
||||
: parseInt(json['current_episode']),
|
||||
historyTime: json['history_time'] == null
|
||||
? null
|
||||
: parseInt(json['history_time']),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'short_id': shortId,
|
||||
'short_play_id': shortPlayId,
|
||||
'name': name,
|
||||
'description': description,
|
||||
'image_url': imageUrl,
|
||||
'horizontally_img': horizontallyImg,
|
||||
'collect_total': collectTotal,
|
||||
'watch_total': watchTotal,
|
||||
'episode_total': episodeTotal,
|
||||
'play_seconds': playSeconds,
|
||||
'is_collect': isCollect,
|
||||
'video_url': videoUrl,
|
||||
'video_info': videoInfo,
|
||||
'category': category,
|
||||
'categoryList': categoryList?.map((item) => item.toJson()).toList(),
|
||||
'tag_type': tagType,
|
||||
'episode': episode,
|
||||
'process': process,
|
||||
'buy_type': buyType,
|
||||
'search_click_total': searchClickTotal,
|
||||
'short_play_video_id': shortPlayVideoId,
|
||||
'current_episode': currentEpisode,
|
||||
'history_time': historyTime,
|
||||
};
|
||||
}
|
||||
|
||||
SkywoodDrama copyWith({
|
||||
int? id,
|
||||
int? shortId,
|
||||
int? shortPlayId,
|
||||
String? name,
|
||||
String? description,
|
||||
String? imageUrl,
|
||||
String? horizontallyImg,
|
||||
int? collectTotal,
|
||||
int? watchTotal,
|
||||
int? episodeTotal,
|
||||
String? playSeconds,
|
||||
bool? isCollect,
|
||||
String? videoUrl,
|
||||
Map<String, dynamic>? videoInfo,
|
||||
List<String>? category,
|
||||
List<SkywoodCategory>? categoryList,
|
||||
String? tagType,
|
||||
int? episode,
|
||||
int? process,
|
||||
int? buyType,
|
||||
int? searchClickTotal,
|
||||
int? shortPlayVideoId,
|
||||
int? currentEpisode,
|
||||
int? historyTime,
|
||||
}) {
|
||||
return SkywoodDrama(
|
||||
id: id ?? this.id,
|
||||
shortId: shortId ?? this.shortId,
|
||||
shortPlayId: shortPlayId ?? this.shortPlayId,
|
||||
name: name ?? this.name,
|
||||
description: description ?? this.description,
|
||||
imageUrl: imageUrl ?? this.imageUrl,
|
||||
horizontallyImg: horizontallyImg ?? this.horizontallyImg,
|
||||
collectTotal: collectTotal ?? this.collectTotal,
|
||||
watchTotal: watchTotal ?? this.watchTotal,
|
||||
episodeTotal: episodeTotal ?? this.episodeTotal,
|
||||
playSeconds: playSeconds ?? this.playSeconds,
|
||||
isCollect: isCollect ?? this.isCollect,
|
||||
videoUrl: videoUrl ?? this.videoUrl,
|
||||
videoInfo: videoInfo ?? this.videoInfo,
|
||||
category: category ?? this.category,
|
||||
categoryList: categoryList ?? this.categoryList,
|
||||
tagType: tagType ?? this.tagType,
|
||||
episode: episode ?? this.episode,
|
||||
process: process ?? this.process,
|
||||
buyType: buyType ?? this.buyType,
|
||||
searchClickTotal: searchClickTotal ?? this.searchClickTotal,
|
||||
shortPlayVideoId: shortPlayVideoId ?? this.shortPlayVideoId,
|
||||
currentEpisode: currentEpisode ?? this.currentEpisode,
|
||||
historyTime: historyTime ?? this.historyTime,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import '../model_parser.dart';
|
||||
|
||||
class SkywoodEpisode {
|
||||
final int id;
|
||||
final int shortId;
|
||||
final int shortPlayId;
|
||||
final int shortPlayVideoId;
|
||||
final int episode;
|
||||
final String videoUrl;
|
||||
final int? promiseViewAd;
|
||||
final int iaaPopUp;
|
||||
bool isLock;
|
||||
String playSeconds;
|
||||
int? viewAdCount;
|
||||
|
||||
SkywoodEpisode({
|
||||
required this.id,
|
||||
required this.shortId,
|
||||
required this.shortPlayId,
|
||||
required this.shortPlayVideoId,
|
||||
required this.episode,
|
||||
required this.videoUrl,
|
||||
required this.playSeconds,
|
||||
required this.isLock,
|
||||
this.promiseViewAd,
|
||||
required this.iaaPopUp,
|
||||
required this.viewAdCount,
|
||||
});
|
||||
|
||||
factory SkywoodEpisode.fromJson(Map<String, dynamic> json) {
|
||||
return SkywoodEpisode(
|
||||
id: parseInt(json['id']),
|
||||
shortId: parseInt(json['short_id']),
|
||||
shortPlayId: parseInt(json['short_play_id']),
|
||||
shortPlayVideoId: parseInt(json['short_play_video_id']),
|
||||
episode: parseInt(json['episode']),
|
||||
videoUrl: parseString(json['video_url']),
|
||||
playSeconds: parseString(json['play_seconds']),
|
||||
isLock: parseBool(json['is_lock']),
|
||||
promiseViewAd: json['promise_view_ad'] == null
|
||||
? null
|
||||
: parseInt(json['promise_view_ad']),
|
||||
iaaPopUp: parseInt(json['iaa_pop_up']),
|
||||
viewAdCount: parseInt(json['view_ad_count']),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'short_id': shortId,
|
||||
'short_play_id': shortPlayId,
|
||||
'short_play_video_id': shortPlayVideoId,
|
||||
'episode': episode,
|
||||
'video_url': videoUrl,
|
||||
'promise_view_ad': promiseViewAd,
|
||||
'iaa_pop_up': iaaPopUp,
|
||||
'is_lock': isLock,
|
||||
'play_seconds': playSeconds,
|
||||
'view_ad_count': viewAdCount,
|
||||
};
|
||||
}
|
||||
|
||||
SkywoodEpisode copyWith({
|
||||
int? id,
|
||||
int? shortId,
|
||||
int? shortPlayId,
|
||||
int? shortPlayVideoId,
|
||||
int? episode,
|
||||
String? videoUrl,
|
||||
bool? isLock,
|
||||
String? playSeconds,
|
||||
String? durationSeconds,
|
||||
int? promiseViewAd,
|
||||
int? viewAdCount,
|
||||
int? iaaPopUp,
|
||||
}) {
|
||||
return SkywoodEpisode(
|
||||
id: id ?? this.id,
|
||||
shortId: shortId ?? this.shortId,
|
||||
shortPlayId: shortPlayId ?? this.shortPlayId,
|
||||
shortPlayVideoId: shortPlayVideoId ?? this.shortPlayVideoId,
|
||||
episode: episode ?? this.episode,
|
||||
videoUrl: videoUrl ?? this.videoUrl,
|
||||
isLock: isLock ?? this.isLock,
|
||||
playSeconds: playSeconds ?? this.playSeconds,
|
||||
promiseViewAd: promiseViewAd ?? this.promiseViewAd,
|
||||
viewAdCount: viewAdCount ?? this.viewAdCount,
|
||||
iaaPopUp: iaaPopUp ?? this.iaaPopUp,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import 'package:skywood/utils/models/drama/skywood_drama.dart';
|
||||
|
||||
class SkywoodHome {
|
||||
final String? moduleKey;
|
||||
final List<SkywoodDrama> dataList;
|
||||
|
||||
const SkywoodHome({required this.moduleKey, required this.dataList});
|
||||
|
||||
factory SkywoodHome.fromJson(Map<String, dynamic> json) {
|
||||
// Map<String, dynamic>? map = json['data'];
|
||||
// List? list = map!['list'];
|
||||
// List<SkywoodDrama> datas = [];
|
||||
// if (list != null) {
|
||||
// datas = list.map((e) => SkywoodDrama.fromJson(e)).toList();
|
||||
// }
|
||||
final data = json['data'];
|
||||
List<SkywoodDrama> videos = [];
|
||||
|
||||
// 🔥 判断 data 的类型
|
||||
if (data is Map<String, dynamic>) {
|
||||
// 情况1: data 是 Map,从中取 list
|
||||
final list = data['list'];
|
||||
if (list is List) {
|
||||
videos = list.map((e) => SkywoodDrama.fromJson(e)).toList();
|
||||
}
|
||||
} else if (data is List) {
|
||||
// 情况2: data 直接就是 List
|
||||
videos = data.map((e) => SkywoodDrama.fromJson(e)).toList();
|
||||
}
|
||||
return SkywoodHome(
|
||||
moduleKey: json['module_key'] as String?,
|
||||
dataList: videos,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {'module_key': moduleKey, 'dataList': dataList};
|
||||
}
|
||||
|
||||
SkywoodHome copyWith({String? moduleKey, dynamic data}) {
|
||||
return SkywoodHome(
|
||||
moduleKey: moduleKey ?? this.moduleKey,
|
||||
dataList: data ?? dataList,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
bool parseBool(dynamic value) {
|
||||
if (value == null) return false;
|
||||
if (value is bool) return value;
|
||||
if (value is num) return value != 0;
|
||||
if (value is String) {
|
||||
final v = value.trim().toLowerCase();
|
||||
return v == 'true' || v == '1' || v == 'yes' || v == 'y';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int parseInt(dynamic value) {
|
||||
if (value == null) return 0;
|
||||
if (value is int) return value;
|
||||
if (value is double) return value.toInt();
|
||||
if (value is num) return value.toInt();
|
||||
if (value is String) return int.tryParse(value) ?? 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
String parseString(dynamic value) {
|
||||
if (value == null) return '';
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
List<String>? parseStringList(dynamic value) {
|
||||
if (value == null) return null;
|
||||
if (value is List) {
|
||||
return value.map((item) => parseString(item)).toList();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
List<int>? parseIntList(dynamic value) {
|
||||
if (value == null) return null;
|
||||
if (value is List) {
|
||||
return value.map((item) => parseInt(item)).toList();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<String, dynamic>? parseMap(dynamic value) {
|
||||
if (value is Map<String, dynamic>) return value;
|
||||
if (value is Map) return Map<String, dynamic>.from(value);
|
||||
return null;
|
||||
}
|
||||
|
||||
List<T> parseModelList<T>(
|
||||
dynamic value,
|
||||
T Function(Map<String, dynamic> json) fromJson,
|
||||
) {
|
||||
if (value is! List) return <T>[];
|
||||
return value
|
||||
.whereType<Map>()
|
||||
.map((item) => fromJson(Map<String, dynamic>.from(item)))
|
||||
.toList();
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import '../model_parser.dart';
|
||||
|
||||
class GlimzoUser {
|
||||
final String customerId;
|
||||
final bool isTourist;
|
||||
final String userLevel;
|
||||
final String avator;
|
||||
final String? familyName;
|
||||
final String? givingName;
|
||||
|
||||
const GlimzoUser({
|
||||
required this.customerId,
|
||||
required this.isTourist,
|
||||
required this.userLevel,
|
||||
required this.avator,
|
||||
this.familyName,
|
||||
this.givingName,
|
||||
});
|
||||
|
||||
factory GlimzoUser.fromJson(Map<String, dynamic> json) {
|
||||
return GlimzoUser(
|
||||
customerId: parseString(json['customer_id']),
|
||||
isTourist: parseBool(json['is_tourist']),
|
||||
userLevel: parseString(json['user_level']),
|
||||
avator: parseString(json['avator']),
|
||||
familyName: json['family_name'] as String?,
|
||||
givingName: parseString(json['giving_name']),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'customer_id': customerId,
|
||||
'is_tourist': isTourist,
|
||||
'user_level': userLevel,
|
||||
'avator': avator,
|
||||
'family_name': familyName,
|
||||
'giving_name': givingName,
|
||||
};
|
||||
}
|
||||
|
||||
GlimzoUser copyWith({
|
||||
String? customerId,
|
||||
bool? isTourist,
|
||||
String? userLevel,
|
||||
String? avator,
|
||||
String? familyName,
|
||||
String? givingName,
|
||||
}) {
|
||||
return GlimzoUser(
|
||||
customerId: customerId ?? this.customerId,
|
||||
isTourist: isTourist ?? this.isTourist,
|
||||
userLevel: userLevel ?? this.userLevel,
|
||||
avator: avator ?? this.avator,
|
||||
familyName: familyName ?? this.familyName,
|
||||
givingName: givingName ?? this.givingName,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
|
||||
class RegisterBean {
|
||||
final String token;
|
||||
final int customerId;
|
||||
final bool? autoLogin;
|
||||
final int touristId;
|
||||
|
||||
RegisterBean({required this.token, required this.customerId, this.autoLogin, required this.touristId});
|
||||
|
||||
factory RegisterBean.fromJson(Map<String, dynamic> json) {
|
||||
return RegisterBean(
|
||||
token: json['token'],
|
||||
customerId: json['customer_id'],
|
||||
autoLogin: json['auto_login'],
|
||||
touristId: json['tourist_id'],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,8 @@ import 'package:get/get.dart';
|
||||
|
||||
/// AppScreen class
|
||||
class AppScreen {
|
||||
static double get screenWidth => Get.width;
|
||||
static double get screenHeight => Get.height;
|
||||
static double get screenWidth => _viewSize.width;
|
||||
static double get screenHeight => _viewSize.height;
|
||||
// static double get screenWidth => MediaQuery.of(Get.context!).size.width;
|
||||
// static double get screenHeight => MediaQuery.of(Get.context!).size.height;
|
||||
|
||||
@@ -13,7 +13,7 @@ class AppScreen {
|
||||
return MediaQuery.of(context).size;
|
||||
}
|
||||
|
||||
static bool get isDebug=> kDebugMode;
|
||||
static bool get isDebug => kDebugMode;
|
||||
|
||||
static double get statusBarHeight => Get.statusBarHeight / Get.pixelRatio;
|
||||
|
||||
@@ -33,12 +33,19 @@ class AppScreen {
|
||||
return GetPlatform.isIOS;
|
||||
}
|
||||
|
||||
static Size get _viewSize {
|
||||
if (Get.context != null) {
|
||||
return MediaQuery.of(Get.context!).size;
|
||||
}
|
||||
|
||||
final view = WidgetsBinding.instance.platformDispatcher.views.first;
|
||||
return view.physicalSize / view.devicePixelRatio;
|
||||
}
|
||||
|
||||
static double aRatioWidth({double? h, double aspectRatio = 1}) {
|
||||
h ??= screenHeight;
|
||||
return screenHeight * 1 / aspectRatio;
|
||||
}
|
||||
|
||||
|
||||
static double get keyboardHeight => Get.mediaQuery.viewInsets.bottom;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
// import 'package:lottie/lottie.dart';
|
||||
|
||||
// 加载数据的函数类型
|
||||
typedef LoadBlock<T> = Future<T> Function();
|
||||
|
||||
// 弹出Toast提示的类
|
||||
class AppToast {
|
||||
static const String _defaultLoadingText = "";
|
||||
static const String _loadingText = "loading...";
|
||||
|
||||
/// 显示普通 Toast 提示
|
||||
static Future<void> showToast(
|
||||
Object msg, {
|
||||
Duration displayTime = const Duration(seconds: 2),
|
||||
}) async {
|
||||
await dismiss();
|
||||
return SmartDialog.showToast(
|
||||
msg.toString(),
|
||||
alignment: Alignment.center,
|
||||
debounce: true,
|
||||
displayTime: displayTime,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<void> showError(
|
||||
Object msg, {
|
||||
Duration displayTime = const Duration(seconds: 2),
|
||||
}) {
|
||||
return showFail(msg, displayTime: displayTime);
|
||||
}
|
||||
|
||||
/// 显示失败提示
|
||||
static Future<void> showFail(
|
||||
Object msg, {
|
||||
Duration displayTime = const Duration(seconds: 2),
|
||||
Function()? onDismiss,
|
||||
}) async {
|
||||
await dismiss();
|
||||
return SmartDialog.showNotify(
|
||||
msg: msg.toString(),
|
||||
alignment: Alignment.center,
|
||||
notifyType: NotifyType.failure,
|
||||
displayTime: displayTime,
|
||||
debounce: true,
|
||||
onDismiss: onDismiss,
|
||||
);
|
||||
}
|
||||
|
||||
/// 显示成功提示
|
||||
static Future<T?> showSuccess<T>(
|
||||
Object msg, {
|
||||
Duration displayTime = const Duration(seconds: 2),
|
||||
}) async {
|
||||
await dismiss();
|
||||
return SmartDialog.showNotify<T>(
|
||||
msg: msg.toString(),
|
||||
notifyType: NotifyType.success,
|
||||
debounce: true,
|
||||
displayTime: displayTime,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<T?> showInfo<T>(
|
||||
Object msg, {
|
||||
Duration displayTime = const Duration(seconds: 2),
|
||||
}) {
|
||||
return showWarning<T>(msg, displayTime: displayTime);
|
||||
}
|
||||
|
||||
static Future<T?> showWarning<T>(
|
||||
Object msg, {
|
||||
Duration displayTime = const Duration(seconds: 2),
|
||||
}) async {
|
||||
await dismiss();
|
||||
return SmartDialog.showNotify<T>(
|
||||
msg: msg.toString(),
|
||||
notifyType: NotifyType.warning,
|
||||
debounce: true,
|
||||
displayTime: displayTime,
|
||||
);
|
||||
}
|
||||
|
||||
/// 显示 Lottie 加载提示
|
||||
// static Future<T?> showLottieLoading<T>({
|
||||
// Duration? displayTime,
|
||||
// }) async {
|
||||
// await dismiss();
|
||||
// return SmartDialog.showLoading<T>(
|
||||
// displayTime: displayTime ?? const Duration(seconds: 5),
|
||||
// maskColor: Colors.transparent,
|
||||
// builder: (_) {
|
||||
// return SizedBox(
|
||||
// width: 120,
|
||||
// height: 120,
|
||||
// child: Lottie.asset(
|
||||
// 'assets/bagua.json',
|
||||
// fit: BoxFit.contain,
|
||||
// repeat: true,
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
|
||||
/// 显示加载提示
|
||||
static Future<T?> showLoading<T>({
|
||||
String text = _loadingText,
|
||||
Duration displayTime = const Duration(seconds: 10),
|
||||
}) async {
|
||||
await dismiss();
|
||||
return SmartDialog.showLoading<T>(msg: text, displayTime: displayTime);
|
||||
}
|
||||
|
||||
/// 关闭特定状态的弹窗
|
||||
static Future<void> dismiss<T>({SmartStatus status = SmartStatus.loading}) {
|
||||
return SmartDialog.dismiss<T>(status: status);
|
||||
}
|
||||
|
||||
/// 关闭所有弹窗(占位)
|
||||
static void dismissAll() {
|
||||
SmartDialog.dismiss();
|
||||
}
|
||||
|
||||
/// 自动显示加载框并在完成后关闭
|
||||
static Future<T> loading<T>(
|
||||
LoadBlock<T> block, {
|
||||
String? text,
|
||||
bool isLoading = true,
|
||||
}) async {
|
||||
if (isLoading) {
|
||||
showLoading(text: text ?? _defaultLoadingText);
|
||||
}
|
||||
|
||||
try {
|
||||
final result = await block();
|
||||
return result;
|
||||
} finally {
|
||||
dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,22 @@ class ConstImg {
|
||||
static const String tabMineUnselect = "assets/common/tab_mine_unselect.png";
|
||||
static const String homeBg = "assets/imgs/home_bg.png";
|
||||
|
||||
// home_card_bg
|
||||
static const String homeCardBg = "assets/imgs/home_card_bg.png";
|
||||
// home_top_bg
|
||||
static const String homeTopBg = "assets/imgs/home_top_bg.png";
|
||||
// home_title_daily/hot/hot_cate/mystery/weekly
|
||||
static const String homeTitleDaily = "assets/imgs/home_title_daily.png";
|
||||
static const String homeTitleHot = "assets/imgs/home_title_hot.png";
|
||||
static const String homeTitleHotCate = "assets/imgs/home_title_hot_cate.png";
|
||||
static const String homeTitleMystery = "assets/imgs/home_title_mystery.png";
|
||||
static const String homeTitleWeekly = "assets/imgs/home_title_weekly.png";
|
||||
static const String homeOpenNowBox = "assets/imgs/home_opennow_box.png";
|
||||
// splash_page
|
||||
static const String splashPage = "assets/imgs/splash_page.png";
|
||||
// heart
|
||||
static const String heart = "assets/images/heart.png";
|
||||
|
||||
// nodata
|
||||
static const String noData = "assets/images/nodata.png";
|
||||
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// navigator
|
||||
class HelpNav {
|
||||
/// clear all route
|
||||
static Future<T?>? offAllNamed<T>(
|
||||
String newRouteName, {
|
||||
Map? arguments,
|
||||
RoutePredicate? predicate,
|
||||
String? tag,
|
||||
Map? parameters,
|
||||
}) {
|
||||
return Get.offAllNamed<T>(
|
||||
newRouteName,
|
||||
arguments: arguments,
|
||||
predicate: predicate,
|
||||
parameters: {...?parameters},
|
||||
);
|
||||
}
|
||||
|
||||
static Future<T?>? off<T>(Widget page, {arguments, preventDuplicates}) {
|
||||
return Get.off<T>(
|
||||
() => page,
|
||||
arguments: arguments,
|
||||
preventDuplicates: preventDuplicates ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<T?>? offAll<T>(Widget page, {arguments, preventDuplicates}) {
|
||||
return Get.offAll<T>(() => page, arguments: arguments);
|
||||
}
|
||||
|
||||
static Future<T?>? offNamed<T>(
|
||||
String newRouteName, {
|
||||
arguments,
|
||||
preventDuplicates,
|
||||
}) {
|
||||
return Get.offNamed<T>(
|
||||
newRouteName,
|
||||
arguments: arguments,
|
||||
preventDuplicates: preventDuplicates ?? true,
|
||||
);
|
||||
}
|
||||
|
||||
/// to name
|
||||
static Future<T?>? toNamed<T>(
|
||||
String newRouteName, {
|
||||
arguments,
|
||||
String? tag,
|
||||
Map? parameters,
|
||||
}) {
|
||||
return Get.toNamed<T>(
|
||||
newRouteName,
|
||||
arguments: arguments,
|
||||
parameters: {...?parameters},
|
||||
);
|
||||
}
|
||||
|
||||
/// to route
|
||||
static Future<T?>? to<T>(Widget page, {arguments, binding}) {
|
||||
return Get.to<T>(
|
||||
() => page,
|
||||
arguments: arguments,
|
||||
binding: binding,
|
||||
preventDuplicates: false,
|
||||
);
|
||||
}
|
||||
|
||||
static void _until<T>({String? routeName, T? result}) {
|
||||
if (result == null) {
|
||||
Get.back<T>(result: result);
|
||||
return;
|
||||
}
|
||||
// Get.back<T>(result: result);
|
||||
//
|
||||
if (routeName == null) {
|
||||
Get.back<T>(result: result);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// back pre
|
||||
/// [result]
|
||||
static void back<T>({T? result, String? routeName}) {
|
||||
_until(routeName: routeName, result: result);
|
||||
}
|
||||
|
||||
/// show dialog
|
||||
static Future<T?> showDialog<T>(
|
||||
Widget child, {
|
||||
bool barrierDismissible = false,
|
||||
}) {
|
||||
return Get.dialog<T>(
|
||||
Center(child: child),
|
||||
barrierDismissible: barrierDismissible,
|
||||
);
|
||||
}
|
||||
|
||||
/// show bottom sheet
|
||||
static Future<T?> showBottom<T>(
|
||||
Widget child, {
|
||||
Color? safeAreaColor,
|
||||
|
||||
double? borderRadius,
|
||||
bool isDismissible = false,
|
||||
bool isScrollControlled = false,
|
||||
bool? enableDrag,
|
||||
}) {
|
||||
return Get.bottomSheet<T>(
|
||||
child,
|
||||
isScrollControlled: isScrollControlled,
|
||||
enableDrag: enableDrag ?? true,
|
||||
isDismissible: isDismissible,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:android_id/android_id.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:skywood/global/request/api_const.dart';
|
||||
|
||||
@immutable
|
||||
class SkyDeviceInfo {
|
||||
final String deviceId;
|
||||
final String systemType;
|
||||
final String systemVersion;
|
||||
final String brand;
|
||||
final String model;
|
||||
|
||||
final String appName;
|
||||
final String appPackageName;
|
||||
final String appVersion;
|
||||
final String idfv;
|
||||
|
||||
static SkyDeviceInfo? _instance;
|
||||
|
||||
const SkyDeviceInfo._({
|
||||
required this.deviceId,
|
||||
required this.systemType,
|
||||
required this.systemVersion,
|
||||
required this.brand,
|
||||
required this.model,
|
||||
required this.appName,
|
||||
required this.appPackageName,
|
||||
required this.appVersion,
|
||||
required this.idfv,
|
||||
});
|
||||
|
||||
static SkyDeviceInfo get instance {
|
||||
if (_instance == null) {
|
||||
throw StateError('error');
|
||||
}
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
static void setMockInstance({
|
||||
String deviceId = 'test-device-id',
|
||||
String systemType = 'test',
|
||||
String systemVersion = '1.0',
|
||||
String brand = 'test',
|
||||
String model = 'test',
|
||||
String appName = 'Glimzo',
|
||||
String appPackageName = 'com.example.skywood',
|
||||
String appVersion = '1.0.0',
|
||||
String idfv = 'test-idfv',
|
||||
}) {
|
||||
_instance = SkyDeviceInfo._(
|
||||
deviceId: deviceId,
|
||||
systemType: systemType,
|
||||
systemVersion: systemVersion,
|
||||
brand: brand,
|
||||
model: model,
|
||||
appName: appName,
|
||||
appPackageName: appPackageName,
|
||||
appVersion: appVersion,
|
||||
idfv: idfv,
|
||||
);
|
||||
}
|
||||
|
||||
/// 初始化设备信息
|
||||
static Future<void> init() async {
|
||||
if (_instance != null) return;
|
||||
|
||||
String deviceId = '',
|
||||
systemType = '',
|
||||
systemVersion = '',
|
||||
brand = '',
|
||||
model = '',
|
||||
appName = 'Glimzo',
|
||||
appPackageName = '',
|
||||
appVersion = '',
|
||||
idfv = '';
|
||||
|
||||
// 获取应用信息
|
||||
await _initAppInfo()
|
||||
.then((info) {
|
||||
appPackageName = info['packageName'] ?? '';
|
||||
appVersion = info['version'] ?? '';
|
||||
appName = ApiConst.appName;
|
||||
})
|
||||
.catchError((e) {
|
||||
debugPrint('[sDeviceInfo] getElPackageName: $e');
|
||||
});
|
||||
|
||||
// 获取设备信息
|
||||
if (Platform.isAndroid) {
|
||||
await _initAndroidInfo()
|
||||
.then((info) {
|
||||
deviceId = info['deviceId'] ?? '';
|
||||
systemType = 'android';
|
||||
systemVersion = info['systemVersion'] ?? '';
|
||||
brand = info['brand'] ?? '';
|
||||
model = info['model'] ?? '';
|
||||
})
|
||||
.catchError((e) {
|
||||
debugPrint('[GlimzoUtilDeviceInfo] Android: $e');
|
||||
});
|
||||
} else if (Platform.isIOS) {
|
||||
await _initIOSInfo()
|
||||
.then((info) {
|
||||
deviceId = info['deviceId'] ?? '';
|
||||
idfv = info['idfv'] ?? '';
|
||||
systemType = 'ios';
|
||||
systemVersion = info['systemVersion'] ?? '';
|
||||
brand = info['brand'] ?? '';
|
||||
model = info['model'] ?? '';
|
||||
})
|
||||
.catchError((e) {
|
||||
debugPrint('[GlimzoUtilDeviceInfo] iOS: $e');
|
||||
});
|
||||
}
|
||||
|
||||
_instance = SkyDeviceInfo._(
|
||||
deviceId: deviceId,
|
||||
systemType: systemType,
|
||||
systemVersion: systemVersion,
|
||||
brand: brand,
|
||||
model: model,
|
||||
appName: appName,
|
||||
appPackageName: appPackageName,
|
||||
appVersion: appVersion,
|
||||
idfv: idfv,
|
||||
);
|
||||
}
|
||||
|
||||
/// 获取应用信息
|
||||
static Future<Map<String, String>> _initAppInfo() async {
|
||||
final packageInfo = await PackageInfo.fromPlatform();
|
||||
return {
|
||||
'packageName': packageInfo.packageName,
|
||||
'version': packageInfo.version,
|
||||
};
|
||||
}
|
||||
|
||||
/// 获取 Android 设备信息
|
||||
static Future<Map<String, String?>> _initAndroidInfo() async {
|
||||
final plugin = DeviceInfoPlugin();
|
||||
final androidInfo = await plugin.androidInfo;
|
||||
final androidIdPlugin = AndroidId();
|
||||
final androidId = await androidIdPlugin.getId();
|
||||
return {
|
||||
'deviceId': androidId ?? androidInfo.id,
|
||||
'systemVersion': androidInfo.version.release,
|
||||
'brand': androidInfo.brand,
|
||||
'model': androidInfo.model,
|
||||
};
|
||||
}
|
||||
|
||||
/// 获取 iOS 设备信息
|
||||
static Future<Map<String, String?>> _initIOSInfo() async {
|
||||
final plugin = DeviceInfoPlugin();
|
||||
final iosInfo = await plugin.iosInfo;
|
||||
final secureStorage = FlutterSecureStorage();
|
||||
String? idfvStore = await secureStorage.read(key: 'identifierForVendor');
|
||||
idfvStore ??= iosInfo.identifierForVendor;
|
||||
|
||||
if (idfvStore != null) {
|
||||
await secureStorage.write(key: 'identifierForVendor', value: idfvStore);
|
||||
}
|
||||
|
||||
return {
|
||||
'deviceId': idfvStore,
|
||||
'idfv': iosInfo.identifierForVendor ?? '',
|
||||
'systemVersion': iosInfo.systemVersion,
|
||||
'brand': iosInfo.model,
|
||||
'model': iosInfo.modelName,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
|
||||
class SkyTimeTool {
|
||||
|
||||
static String getTimeZone() {
|
||||
final offset = DateTime.now().timeZoneOffset;
|
||||
final hours = offset.inHours;
|
||||
final minutes = offset.inMinutes.remainder(60).abs();
|
||||
final sign = hours >= 0 ? '+' : '-';
|
||||
final formatted =
|
||||
'GMT$sign${hours.abs().toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}';
|
||||
return formatted;
|
||||
}
|
||||
|
||||
//格式化数字,带单位 k / m,可自定义小数位和千分位分隔符
|
||||
static String formatNum(
|
||||
num value, {
|
||||
int decimalDigits = 1,
|
||||
String separator = ',',
|
||||
}) {
|
||||
String removeTrailingZeros(String str) {
|
||||
if (str.contains('.')) {
|
||||
str = str.replaceAll(RegExp(r'0+$'), ''); // 去掉末尾 0
|
||||
str = str.replaceAll(RegExp(r'\.$'), ''); // 去掉末尾小数点
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
String addSeparator(String str) {
|
||||
final parts = str.split('.');
|
||||
final intPart = parts[0];
|
||||
final decPart = parts.length > 1 ? '.${parts[1]}' : '';
|
||||
final regex = RegExp(r'(\d+)(\d{3})');
|
||||
var formatted = intPart;
|
||||
while (regex.hasMatch(formatted)) {
|
||||
formatted = formatted.replaceAllMapped(
|
||||
regex,
|
||||
(m) => '${m[1]}$separator${m[2]}',
|
||||
);
|
||||
}
|
||||
return '$formatted$decPart';
|
||||
}
|
||||
|
||||
if (value < 1000) {
|
||||
return addSeparator(value.toStringAsFixed(0));
|
||||
} else if (value < 1000000) {
|
||||
final formatted = (value / 1000).toStringAsFixed(decimalDigits);
|
||||
return '${removeTrailingZeros(formatted)}K';
|
||||
} else {
|
||||
final formatted = (value / 1000000).toStringAsFixed(decimalDigits);
|
||||
return '${removeTrailingZeros(formatted)}M';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:skywood/global/client/user_client.dart';
|
||||
|
||||
/// Token 管理单例类
|
||||
/// 对应原项目的 GlimzoUtilToken
|
||||
class TokenManager {
|
||||
// ============ 单例模式 ============
|
||||
static final TokenManager _instance = TokenManager._internal();
|
||||
factory TokenManager() => _instance;
|
||||
TokenManager._internal();
|
||||
|
||||
// ============ 常量定义 ============
|
||||
static const String _keyToken = 'SKY_STORE_TOKEN';
|
||||
static const String _keyUserInfo = 'SKY_STORE_USER_INFO';
|
||||
|
||||
// ============ 内存缓存 ============
|
||||
String? _cachedToken;
|
||||
Map<String, dynamic>? _cachedUserInfo;
|
||||
|
||||
// ============ 核心方法 ============
|
||||
|
||||
/// 获取 Token(从内存或存储)
|
||||
String get() {
|
||||
if (_cachedToken != null) {
|
||||
return _cachedToken!;
|
||||
}
|
||||
// 如果内存中没有,从存储中加载
|
||||
_cachedToken = _getFromStorage(_keyToken);
|
||||
return _cachedToken ?? '';
|
||||
}
|
||||
|
||||
/// 保存 Token(到内存和存储)
|
||||
void save(String token) {
|
||||
if (token.isNotEmpty) {
|
||||
_cachedToken = token;
|
||||
_saveToStorage(_keyToken, token);
|
||||
}
|
||||
}
|
||||
|
||||
/// 清除 Token(从内存和存储)
|
||||
void clear() {
|
||||
_cachedToken = null;
|
||||
_removeFromStorage(_keyToken);
|
||||
_cachedUserInfo = null;
|
||||
_removeFromStorage(_keyUserInfo);
|
||||
}
|
||||
|
||||
/// 刷新 Token(调用注册接口获取新 Token)
|
||||
/// 注意:这里需要传入一个刷新函数,因为原项目依赖 GlimzoRequestUser
|
||||
Future<void> update() async {
|
||||
try {
|
||||
final res = await UserClient.register();
|
||||
if (res == null) return;
|
||||
final newToken = res.token;
|
||||
if (newToken.isNotEmpty ?? false) {
|
||||
// 保存新 Token
|
||||
save(newToken);
|
||||
// 如果返回了用户信息,也一并保存
|
||||
// if (res['userInfo'] != null) {
|
||||
// saveUserInfo(res['userInfo']);
|
||||
// }
|
||||
} else {
|
||||
throw Exception('Invalid token response');
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('[TokenManager] Error updating token: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
// ============ 便捷属性 ============
|
||||
|
||||
/// 判断 Token 是否为空
|
||||
bool get isEmpty => get().isEmpty;
|
||||
|
||||
/// 判断 Token 是否不为空
|
||||
bool get isNotEmpty => !isEmpty;
|
||||
|
||||
/// 获取完整 Authorization 头
|
||||
String? get authorizationHeader {
|
||||
final token = get();
|
||||
if (token.isNotEmpty) {
|
||||
return 'Bearer $token';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ============ 用户信息扩展(原项目没有,但建议加上) ============
|
||||
|
||||
/// 保存用户信息
|
||||
void saveUserInfo(Map<String, dynamic> userInfo) {
|
||||
_cachedUserInfo = userInfo;
|
||||
_saveToStorage(_keyUserInfo, jsonEncode(userInfo));
|
||||
}
|
||||
|
||||
/// 获取用户信息
|
||||
Map<String, dynamic>? getUserInfo() {
|
||||
if (_cachedUserInfo != null) {
|
||||
return _cachedUserInfo;
|
||||
}
|
||||
final stored = _getFromStorage(_keyUserInfo);
|
||||
if (stored != null && stored.isNotEmpty) {
|
||||
try {
|
||||
_cachedUserInfo = jsonDecode(stored) as Map<String, dynamic>;
|
||||
return _cachedUserInfo;
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ============ 私有存储方法 ============
|
||||
|
||||
String? _getFromStorage(String key) {
|
||||
try {
|
||||
// 使用 SharedPreferences 同步获取(但 SharedPreferences 是异步的)
|
||||
// 为了保持与原项目一致的同步接口,这里使用异步转同步
|
||||
// 但更好的方式是在初始化时预加载
|
||||
return _getFromStorageSync(key);
|
||||
} catch (e) {
|
||||
debugPrint('[TokenManager] Error reading from storage: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 同步读取(注意:SharedPreferences 本身是异步的,这里用同步方式需要谨慎)
|
||||
String? _getFromStorageSync(String key) {
|
||||
// 由于 SharedPreferences 是异步的,我们使用一个全局缓存
|
||||
// 实际项目中建议在 main 中调用 init() 预加载
|
||||
try {
|
||||
// 这里为了简化,直接返回 null,实际应该用异步方式
|
||||
// 建议使用下面的异步版本
|
||||
return null;
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
void _saveToStorage(String key, String value) {
|
||||
// 异步保存,不等待结果
|
||||
_saveToStorageAsync(key, value);
|
||||
}
|
||||
|
||||
void _removeFromStorage(String key) {
|
||||
// 异步移除,不等待结果
|
||||
_removeFromStorageAsync(key);
|
||||
}
|
||||
|
||||
// ============ 异步存储方法(推荐使用) ============
|
||||
|
||||
/// 初始化:从存储中加载 Token
|
||||
Future<void> init() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
_cachedToken = prefs.getString(_keyToken);
|
||||
|
||||
final userInfoStr = prefs.getString(_keyUserInfo);
|
||||
if (userInfoStr != null && userInfoStr.isNotEmpty) {
|
||||
try {
|
||||
_cachedUserInfo = jsonDecode(userInfoStr) as Map<String, dynamic>;
|
||||
} catch (_) {
|
||||
_cachedUserInfo = null;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('[TokenManager] Init error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// 异步获取 Token
|
||||
Future<String> getAsync() async {
|
||||
if (_cachedToken != null) {
|
||||
return _cachedToken!;
|
||||
}
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
_cachedToken = prefs.getString(_keyToken);
|
||||
return _cachedToken ?? '';
|
||||
} catch (e) {
|
||||
debugPrint('[TokenManager] Get token error: $e');
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/// 异步保存 Token
|
||||
Future<void> saveAsync(String token) async {
|
||||
if (token.isNotEmpty) {
|
||||
_cachedToken = token;
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(_keyToken, token);
|
||||
} catch (e) {
|
||||
debugPrint('[TokenManager] Save token error: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 异步清除 Token
|
||||
Future<void> clearAsync() async {
|
||||
_cachedToken = null;
|
||||
_cachedUserInfo = null;
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.remove(_keyToken);
|
||||
await prefs.remove(_keyUserInfo);
|
||||
} catch (e) {
|
||||
debugPrint('[TokenManager] Clear token error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// 异步保存到存储
|
||||
Future<void> _saveToStorageAsync(String key, String value) async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(key, value);
|
||||
} catch (e) {
|
||||
debugPrint('[TokenManager] Save to storage error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// 异步从存储移除
|
||||
Future<void> _removeFromStorageAsync(String key) async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.remove(key);
|
||||
} catch (e) {
|
||||
debugPrint('[TokenManager] Remove from storage error: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../global/const/common_color.dart';
|
||||
import '../../extend/widget_extend.dart';
|
||||
import '../help_nav.dart';
|
||||
|
||||
|
||||
class SkyText extends StatelessWidget {
|
||||
const SkyText({
|
||||
super.key,
|
||||
required this.text,
|
||||
this.color = Colors.white,
|
||||
this.fontSize = 14,
|
||||
this.shadow,
|
||||
this.decoration,
|
||||
this.decorationColor,
|
||||
this.textAlign,
|
||||
this.fontWeight,
|
||||
this.maxLines,
|
||||
this.lineHeight,
|
||||
this.letterSpacing,
|
||||
this.overflow = TextOverflow.ellipsis,
|
||||
this.softWrap,
|
||||
this.fontFamily,
|
||||
});
|
||||
|
||||
|
||||
final String text;
|
||||
final Color? color;
|
||||
final double? fontSize;
|
||||
final TextDecoration? decoration;
|
||||
final Color? decorationColor;
|
||||
final TextAlign? textAlign;
|
||||
final FontWeight? fontWeight;
|
||||
final int? maxLines;
|
||||
final Shadow? shadow;
|
||||
final double? lineHeight;
|
||||
final double? letterSpacing;
|
||||
final TextOverflow? overflow;
|
||||
final bool? softWrap;
|
||||
final String? fontFamily;
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final FontWeight resolvedWeight = fontWeight ?? FontWeight.normal;
|
||||
|
||||
return Text(text, style: TextStyle(
|
||||
fontSize: fontSize,
|
||||
fontWeight: resolvedWeight,
|
||||
decoration: decoration,
|
||||
fontFamily: fontFamily ?? 'Inter',
|
||||
color: color,
|
||||
shadows: shadow == null ? null : [shadow!],
|
||||
decorationColor: decorationColor,
|
||||
height: lineHeight,
|
||||
letterSpacing: letterSpacing,
|
||||
), maxLines: maxLines,
|
||||
overflow: overflow,
|
||||
softWrap: softWrap,
|
||||
textAlign: textAlign,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class ComConfirmWidget extends StatelessWidget {
|
||||
|
||||
final String? title;
|
||||
final String? content;
|
||||
final Widget? contentWidget;
|
||||
final String? cancelStr, confirmStr;
|
||||
final double? height;
|
||||
final Function? onConfirmed;
|
||||
final Function? onCanceled;
|
||||
final bool? showCancel;
|
||||
final bool? clickHideAlert;
|
||||
|
||||
const ComConfirmWidget({
|
||||
super.key,
|
||||
this.title,
|
||||
this.content,
|
||||
this.contentWidget,
|
||||
this.cancelStr,
|
||||
this.confirmStr,
|
||||
this.height,
|
||||
this.onConfirmed,
|
||||
this.onCanceled,
|
||||
this.showCancel = true,
|
||||
this.clickHideAlert = true,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: height ?? 160,
|
||||
margin: EdgeInsets.only(left: 25, right: 25),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Column(
|
||||
// mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox(height: 15,),
|
||||
|
||||
title==null ? SizedBox() : Padding(padding: EdgeInsets.only(left:15, right: 15, bottom: 5),
|
||||
child: SkyText(text: title!, fontSize: 16, color: Colors.black,
|
||||
)
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Expanded(
|
||||
child: Padding(padding: EdgeInsets.only(left:14, right: 14),
|
||||
child: contentWidget ?? Center(
|
||||
child: SkyText(text: content!,
|
||||
fontSize: 15, color: CommonColor.black,
|
||||
maxLines: 3),
|
||||
),
|
||||
)
|
||||
),
|
||||
|
||||
const SizedBox(height: 10),
|
||||
Divider(thickness: 0.6, height: 1),
|
||||
SizedBox(
|
||||
height: 44,
|
||||
child: IntrinsicHeight(
|
||||
child: showCancel! ? Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
|
||||
TextButton(onPressed: () {
|
||||
Navigator.pop(context);
|
||||
onCanceled?.call();
|
||||
},
|
||||
child: Center(
|
||||
child: SkyText(text: cancelStr ?? 'Cancel',
|
||||
color: Colors.grey, fontSize: 14))
|
||||
),
|
||||
|
||||
VerticalDivider(width: 5, thickness: 0.6,),
|
||||
|
||||
TextButton(onPressed: () {
|
||||
|
||||
if (clickHideAlert == true) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
onConfirmed?.call();
|
||||
},
|
||||
child: Center(
|
||||
child:SkyText(text: confirmStr ?? 'Confirm',
|
||||
color: CommonColor.goldColor, fontSize: 14))
|
||||
),
|
||||
],
|
||||
) : TextButton(onPressed: () {
|
||||
|
||||
Navigator.pop(context);
|
||||
onConfirmed?.call();
|
||||
},
|
||||
child: Center(
|
||||
child:SkyText(text: confirmStr ?? 'Confirm',
|
||||
color: CommonColor.black, fontSize: 14,))
|
||||
),
|
||||
)
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class ComSheetWidget extends StatelessWidget {
|
||||
|
||||
final Function(int)? onSelected;
|
||||
final String? title;
|
||||
final List<String> items;
|
||||
final TextAlign? textAlign;
|
||||
|
||||
const ComSheetWidget({super.key,
|
||||
this.onSelected,
|
||||
this.title,
|
||||
this.textAlign = TextAlign.left,
|
||||
required this.items,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(child: _mainView());
|
||||
}
|
||||
|
||||
_mainView() {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
height: items.length>5 ? 360 : 40+items.length*47 + 50,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(20),
|
||||
topRight: Radius.circular(20)
|
||||
),
|
||||
),
|
||||
child: _ifElseWidget(),
|
||||
);
|
||||
}
|
||||
|
||||
_ifElseWidget() {
|
||||
if (title != null) {
|
||||
return Column(
|
||||
children: [
|
||||
const SizedBox(height: 6),
|
||||
Text('$title', style: TextStyle(
|
||||
color: Colors.black, fontSize: 16
|
||||
)),
|
||||
const SizedBox(height: 5),
|
||||
const Divider(height: 8, thickness: 0.6,),
|
||||
// Divider(),
|
||||
const SizedBox(height: 5),
|
||||
Expanded(child: ListView.separated(itemBuilder: (context, index) {
|
||||
return _itemCreateer(index);
|
||||
|
||||
}, separatorBuilder: (context, index) {
|
||||
return Divider( indent: 15, endIndent: 15, thickness: 0.6,);
|
||||
}, itemCount: items.length))
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return ListView.separated(itemBuilder: (context, index) {
|
||||
return _itemCreateer(index);
|
||||
|
||||
}, separatorBuilder: (context, index) {
|
||||
return Divider(indent: 15, endIndent: 15, thickness: 0.6,);
|
||||
}, itemCount: items.length);
|
||||
}
|
||||
}
|
||||
|
||||
_itemCreateer(int index) {
|
||||
return SizedBox(
|
||||
height: 47,
|
||||
child: Center(
|
||||
child: Text(items[index], style: TextStyle(
|
||||
fontSize: 15, color: Colors.black,
|
||||
)),
|
||||
).addInkWell(
|
||||
onTap: () {
|
||||
|
||||
HelpNav.back();
|
||||
onSelected?.call(index);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user