feat: 整体常用控件封装,常用工具类。首页数据对接
This commit is contained in:
1 parent
d90d023e85
commit
fbcd33ea2b
50 files changed
+4558
-264
No files matched your search
@@ -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