feat: 工具类提交。

This commit is contained in:
phoenix--zhang 2026-07-06 10:45:08 +08:00
parent a2b38fa1e8
commit 557f900187
20 changed files with 2026 additions and 3 deletions

BIN
assets/other/pricary.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
assets/other/web_site.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,573 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:novyronst/common/api/api_string.dart';
import 'package:novyronst/common/api/ny_network_intercept.dart';
import 'package:novyronst/common/help/help_token.dart';
import 'package:novyronst/tools/widgets/app_toast_util.dart';
/// HTTP
class AppService {
static AppService? _instance;
late Dio _dio;
static AppService get instance {
_instance ??= AppService._internal();
return _instance!;
}
AppService._internal() {
_initDio();
}
Dio get dio => _dio;
/// Dio
void _initDio() {
_dio = Dio(BaseOptions(
baseUrl: ApiString.baseUrl,
connectTimeout: const Duration(seconds: 15),
receiveTimeout: const Duration(seconds: 15),
sendTimeout: const Duration(seconds: 15),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
responseType: ResponseType.json,
));
//
_dio.interceptors.add(NyNetworkIntercept());
//
// if (!kReleaseMode) {
// _dio.interceptors.add(TalkerDioLogger(
// settings: TalkerDioLoggerSettings(
// printRequestHeaders: true,
// printRequestData: true,
// printResponseData: true,
// printResponseHeaders: false,
// ),
// ));
// }
}
///
void updateBaseOptions(BaseOptions options) {
_dio.options = options;
}
}
/// ============ ============
class ApiRequest {
static final Dio _dio = AppService.instance.dio;
/// GET
static Future<HttpResult<T>> get<T>(
String path, {
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
int? retry,
}) async {
try {
final response = await _executeRequest(
() => _dio.get(
path,
queryParameters: queryParameters,
options: options,
cancelToken: cancelToken,
),
);
return HttpResult<T>.fromResponse(response);
} on DioException catch (e) {
return _handleError<T>(e);
} catch (e) {
return HttpResult<T>.error(e.toString());
}
}
/// POST
static Future<HttpResult<T>> post<T>(
String path, {
dynamic data,
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
int? retry,
}) async {
try {
final response = await _executeRequest(
() => _dio.post(
path,
data: data,
queryParameters: queryParameters,
options: options,
cancelToken: cancelToken,
),
);
return HttpResult<T>.fromResponse(response);
} on DioException catch (e) {
return _handleError<T>(e);
} catch (e) {
return HttpResult<T>.error(e.toString());
}
}
///
static Future<HttpResult<T>> fileUpload<T>(
String path, {
required File file,
String fieldName = 'file',
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
ProgressCallback? onSendProgress,
int? retry,
}) async {
try {
final formData = FormData.fromMap({
fieldName: await MultipartFile.fromFile(file.path),
});
final response = await _executeRequest(
() => _dio.post(
path,
data: formData,
queryParameters: queryParameters,
options: options ?? Options(contentType: 'multipart/form-data'),
cancelToken: cancelToken,
onSendProgress: onSendProgress,
),
);
return HttpResult<T>.fromResponse(response);
} on DioException catch (e) {
return _handleError<T>(e);
} catch (e) {
return HttpResult<T>.error(e.toString());
}
}
/// ============ fetchPage ============
static Future<List<T>> fetchPage<T>(
String url, {
int? page,
int? pageSize,
Map<String, dynamic>? extraParams,
required T Function(dynamic json) fromJson,
bool error = true,
String method = 'get',
int? retry,
}) async {
try {
final params = {
'current_page': page?.toString() ?? '1',
'page_size': pageSize?.toString() ?? '20',
...?extraParams,
};
Response response;
if (method.toLowerCase() == 'get') {
response = await _executeRequest(
() => _dio.get(url, queryParameters: params),
);
} else {
response = await _executeRequest(
() => _dio.post(url, data: params),
);
}
if (response.statusCode != 200) {
throw Exception("HTTP ${response.statusCode}");
}
final data = response.data;
if (data is Map<String, dynamic>) {
final base = BasePageResponse.fromJson(data);
if (!base.success) {
if (error) {
// Toast
AppToastUtil.showError(base.msg);
}
throw Exception(base.msg);
}
final result = base.data;
if (result is List) {
return result.map<T>((e) => fromJson(e)).toList();
} else if (result is Map && result['list'] is List) {
return (result['list'] as List).map<T>((e) => fromJson(e)).toList();
} else {
throw Exception("data error");
}
} else {
throw Exception("format error");
}
} catch (e) {
// _handleError();
rethrow;
}
}
///
static Future<Response> _executeRequest(
Future<Response> Function() requestFn,
) async {
try {
final response = await requestFn();
// 200
if (response.statusCode != 200) {
throw DioException(
requestOptions: response.requestOptions,
response: response,
type: DioExceptionType.badResponse,
error: 'HTTP ${response.statusCode}',
);
}
return response;
} catch (e) {
//
rethrow;
}
}
// ============ ============
static Future<Response> _retryRequest(
Future<Response> Function() requestFn, {
int? retries,
}) async {
int attempt = 0;
final maxRetries = retries ?? 2;
while (attempt <= maxRetries) {
try {
final response = await requestFn();
if (response.statusCode == 200) {
return response;
}
// 401/402
if (response.statusCode == 401 || response.statusCode == 402) {
if (attempt == maxRetries) {
throw DioException(
requestOptions: response.requestOptions,
response: response,
type: DioExceptionType.badResponse,
error: 'errorCode:${response.statusCode}',
);
}
}
if (attempt == maxRetries) {
throw DioException(
requestOptions: response.requestOptions,
response: response,
type: DioExceptionType.badResponse,
error: 'HTTP ${response.statusCode}',
);
}
} catch (e) {
if (attempt == maxRetries) {
rethrow;
}
}
attempt++;
await Future.delayed(const Duration(milliseconds: 300));
}
throw Exception("retry failed (unreachable)");
}
// ============ ============
static HttpResult<T> _handleError<T>(DioException e) {
final Response? resp = e.response;
//
if (resp != null && resp.data != null) {
try {
final parsed = HttpResult<T>.fromResponse(resp);
if (!parsed.success) {
// 401/402
if (resp.statusCode == 401 || resp.statusCode == 402) {
HelpToken().update();
if (resp.statusCode == 402) {
AppToastUtil.showError('账号已在其他设备登录,请重新登录');
}
}
return parsed;
}
} catch (_) {}
}
String message = _getErrorMessage(e);
return HttpResult<T>.error(message, httpStatus: resp?.statusCode);
}
static String _getErrorMessage(DioException e) {
switch (e.type) {
case DioExceptionType.connectionTimeout:
return '连接超时,请稍后重试';
case DioExceptionType.sendTimeout:
return '发送超时,请稍后重试';
case DioExceptionType.receiveTimeout:
return '接收超时,请稍后重试';
case DioExceptionType.badResponse:
final statusCode = e.response?.statusCode;
if (statusCode == 401) {
HelpToken().update();
return '未授权,请重新登录';
}
if (statusCode == 402) {
HelpToken().update();
return '账号已在其他设备登录,请重新登录';
}
return _handleStatusCode(statusCode);
case DioExceptionType.cancel:
return '请求已取消';
case DioExceptionType.connectionError:
return '网络连接失败,请检查网络设置';
default:
return '网络错误,请稍后重试';
}
}
static String _handleStatusCode(int? statusCode) {
switch (statusCode) {
case 400:
return '请求错误';
case 403:
return '拒绝访问';
case 404:
return '请求资源不存在';
case 500:
return '服务器内部错误';
case 502:
return '网关错误';
case 503:
return '服务不可用';
default:
return '请求失败';
}
}
}
/// [isSuccess]
class HttpResult<T> {
final int? status;
final bool success;
final String? message;
final T? data;
final int? errorCode;
const HttpResult({
this.status,
this.success = false,
this.message,
this.data,
this.errorCode,
});
bool get isSuccess => success;
factory HttpResult.fromResponse(Response response) {
try {
final int httpStatus = response.statusCode ?? 0;
final dynamic raw = response.data;
final Map<String, dynamic>? root = _jsonMap(raw);
// { "success": false, "error": { ... } }
if (root != null && root['success'] == false) {
final payload = ApiErrorPayload.tryParse(root);
return HttpResult<T>(
status: httpStatus,
success: false,
message: payload?.message ?? root['message']?.toString() ?? '请求失败',
errorCode: payload?.code,
data: null,
);
}
// HTTP 2xxbody
if (httpStatus >= 200 && httpStatus < 300) {
return HttpResult<T>(
status: httpStatus,
success: true,
message: null,
data: raw as T?,
errorCode: null,
);
}
// 2xx body error
if (root != null) {
final payload = ApiErrorPayload.tryParse(root);
if (payload != null) {
return HttpResult<T>(
status: httpStatus,
success: false,
message: payload.message,
errorCode: payload.code,
data: null,
);
}
}
return HttpResult<T>(
status: httpStatus,
success: false,
message: _handleStatusCode(httpStatus),
data: null,
);
} catch (e) {
return HttpResult<T>(
status: response.statusCode,
success: false,
message: '数据解析失败: $e',
data: null,
);
}
}
factory HttpResult.error(String message, {int? httpStatus}) {
return HttpResult<T>(
success: false,
status: httpStatus,
message: message,
);
}
factory HttpResult.networkError() {
return HttpResult<T>(
success: false,
status: -1,
message: '网络连接失败,请检查网络后重试',
);
}
// ============ ============
static Map<String, dynamic>? _jsonMap(dynamic value) {
if (value is Map<String, dynamic>) return value;
if (value is Map) {
try {
return value.cast<String, dynamic>();
} catch (_) {
return Map<String, dynamic>.from(value as Map);
}
}
return null;
}
static String _handleStatusCode(int? statusCode) {
switch (statusCode) {
case 400:
return '请求错误';
case 401:
return '未授权,请重新登录';
case 403:
return '拒绝访问';
case 404:
return '请求资源不存在';
case 500:
return '服务器内部错误';
case 502:
return '网关错误';
case 503:
return '服务不可用';
default:
return '请求失败';
}
}
}
/// `{ "error": { "code", "details", "message" }, "success": false }`
class ApiErrorPayload {
final int? code;
final dynamic details;
final String? message;
const ApiErrorPayload({this.code, this.details, this.message});
static ApiErrorPayload? tryParse(Map<String, dynamic>? root) {
if (root == null) return null;
final err = root['error'];
if (err is! Map) return null;
final m = _jsonMap(err);
if (m == null) return null;
return ApiErrorPayload(
code: _parseInt(m['code']),
details: m['details'],
message: m['message']?.toString(),
);
}
static Map<String, dynamic>? _jsonMap(dynamic value) {
if (value is Map<String, dynamic>) return value;
if (value is Map) {
try {
return value.cast<String, dynamic>();
} catch (_) {
return Map<String, dynamic>.from(value as Map);
}
}
return null;
}
static int? _parseInt(dynamic v) {
if (v == null) return null;
if (v is int) return v;
if (v is num) return v.toInt();
return int.tryParse(v.toString());
}
}
/// GlimzoGetxBaseResponse
class BasePageResponse<T> {
final int code;
final String msg;
final T? data;
BasePageResponse({
required this.code,
required this.msg,
this.data,
});
bool get success => code == 200;
factory BasePageResponse.fromJson(Map<String, dynamic> json) {
return BasePageResponse(
code: json['code'] ?? -1,
msg: json['msg'] ?? '',
data: json['data'],
);
}
}
/// Map GlimzoMapRes
class MapRes {
static List<T> getList<T>({
required Map<dynamic, dynamic> mapData,
required String alias,
required T Function(Map<String, dynamic>) fromJson,
}) {
final rawList = mapData[alias];
if (rawList is List) {
return rawList
.whereType<Map<String, dynamic>>()
.map(fromJson)
.toList();
}
return <T>[];
}
static T toModel<T>({
required Map<String, dynamic> json,
required T Function(Map<String, dynamic>) fromJson,
}) {
return fromJson(json);
}
}

View File

@ -0,0 +1,53 @@
// https://api-glimzodf.glimzodf.com/glimzodf/
class ApiString {
static const String baseUrl = "https://api-glimzodf.glimzodf.com/";
static const String appName = 'Novyronst';
static String webPrefix = "glimzodf.com";
static String webIndex = "https://$webPrefix";
static String webPrivacy = "https://$webPrefix/private";
static String webAgreement = "https://$webPrefix/user_policy";
static String webmemberAgreement = "https://$webPrefix/member_ship_agreement";
}
class ApiStringUser {
static const String login = "/customer/login";
// /customer/register
static const String register = "/customer/register";
// /customer/info
static const String userInfo = "/customer/info";
static const String eventCreateHistory = "/createHistory";
static const String eventUserEnterApp = "/customer/enterTheApp";
static const String eventUserOnline = "/customer/onLine";
static const String eventUserLeaveApp = "/customer/leaveApp";
static const String eventPlaySeconds = "/uploadHistorySeconds";
static const String eventWatchTime = "/watchShortDuration";
static const String eventReport = "/event/add";
}
class ApiStringVideo {
static const String getRecommandsList = "/getRecommands";
static const String getVideoDetail = "/getVideoDetails";
static const String getHomeModules = "/home/all-modules";
static const String getRankingList = "/homeRanking";
static const String getCategoriesList = "/getCategories";
static const String getCategoriesDetail = "/videoList";
static const String getWaterflowList = "/newShortPlay";
static const String getSearchHots = "/search/hots";
static const String getSearch = "/search";
static const String collect = "/collect";
static const String cancelCollect = "/cancelCollect";
static const String getCollections = "/myCollections";
static const String getHistories = "/myHistorys";
}

View File

@ -0,0 +1,141 @@
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:logger/logger.dart';
import 'package:novyronst/common/help/help_time.dart';
import 'package:novyronst/common/help/help_token.dart';
import '../../tools/util/ny_device_info.dart';
import '../help/help_encrypt.dart';
///
class NyNetworkIntercept extends Interceptor {
final bool enableLog = !kReleaseMode;
final Logger _logger = Logger();
@override
Future<void> onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
//
await _addRequestHeaders(options);
//
_logInfo("🔶 REQUEST [${options.method.toUpperCase()}] ${options.uri}");
_logDebug("Headers: ${options.headers}");
if (options.data != null) {
_logDebug("Payload: ${options.data}");
}
handler.next(options);
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
_logInfo("🟢 RESPONSE [${response.statusCode}] ${response.requestOptions.uri}");
// _logDebug("Body: ${response.data}");
// 401 / 402 Token
if (response.statusCode == 401 || response.statusCode == 402) {
// Token
HelpToken().update();
if (response.statusCode == 402) {
_handleError('errorCode:${response.statusCode}', true);
}
handler.reject(DioException(
requestOptions: response.requestOptions,
response: response,
type: DioExceptionType.badResponse,
error: 'errorCode:${response.statusCode}',
));
return;
}
// Release
if (kReleaseMode && response.data is String) {
try {
final deStr = HelpEncrypt.deStr(response.data);
response.data = jsonDecode(deStr);
} catch (e) {
_logError("解密失败: $e");
}
}
handler.next(response);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
_logError("🔴 Error: ${err.message}");
handler.next(err);
}
// ============ ============
Future<void> _addRequestHeaders(RequestOptions options) async {
// security: false
if (!kReleaseMode) {
options.headers['security'] = 'false';
}
// Token
final tokenManager = HelpToken();
final token = tokenManager.get();
// print('查看token: $token');
if (token.isNotEmpty) {
options.headers['Authorization'] = 'Bearer $token';
}
//
final deviceInfo = NyDeviceInfo.instance;
options.headers.addAll({
'product-prefix': 'glimzo',
'device-id': deviceInfo.deviceId,
'system-type': deviceInfo.systemType,
'model': deviceInfo.model,
'system-version': deviceInfo.systemVersion,
'brand': deviceInfo.brand,
'app-version': deviceInfo.appVersion,
'app-name': deviceInfo.appName,
'lang-key': 'en',
'time-zone': HelpTime.getTimeZone(),
'idfa': '',
'idfv': deviceInfo.idfv,
'device-gaid': '',
});
// prevToken
await _handleOldToken(options);
}
Future<void> _handleOldToken(RequestOptions options) async {
try {
if (options.data is Map<String, dynamic>) {
final body = options.data as Map<String, dynamic>;
final prevToken = body['prevToken'];
if (prevToken != null && prevToken.toString().isNotEmpty) {
options.headers['Authorization'] = prevToken.toString();
}
}
} catch (_) {
// JSON
}
}
void _logInfo(String message) {
if (enableLog) _logger.i(message);
}
void _logDebug(String message) {
if (enableLog) _logger.d('💡 $message');
}
void _logError(String message) {
if (enableLog) _logger.e(message);
}
void _handleError(dynamic e, bool error) {
_logError("🔴 Error: $e");
//
}
}

View File

@ -0,0 +1,41 @@
import 'package:novyronst/common/api/api_request.dart';
import 'package:novyronst/common/api/api_string.dart';
import 'package:novyronst/common/help/help_log.dart';
import 'package:novyronst/common/model/novyronst_user.dart';
import 'package:novyronst/common/model/register_model.dart';
class UserRequest {
static Future login() {
final body = <String, dynamic>{
"avator": '',
"email": '123456@qq.com',
"family_name": 'test1',
"platform": 'android',
"third_id": '32434',
};
return ApiRequest.post(ApiStringUser.login, data: body).then((value) {
HelpLog.print(value.data, tag: 'loginnnn');
});
}
static Future<RegisterModel?> register() {
return ApiRequest.post(ApiStringUser.register).then((value) {
if (value.isSuccess) {
return RegisterModel.fromJson(value.data['data']);
}
return null;
});
}
static Future<NovyronstUser?> getUserInfo() {
return ApiRequest.get(ApiStringUser.userInfo).then((value) {
if (value.isSuccess) {
HelpLog.print(value.data, tag: 'getUserInfo');
return NovyronstUser.fromJson(value.data['data']);
}
return null;
});
}
}

View File

View File

@ -0,0 +1,155 @@
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'dart:math';
class HelpEncrypt {
static const int bfSize = 2048;
static const String enStrTag = r'$';
// Generates a random salt of size between 16 and 64 bytes
static Uint8List randSalt() {
final rand = Random.secure();
final size = rand.nextInt(49) + 16; // 16..64
return Uint8List.fromList(List.generate(size, (_) => rand.nextInt(256)));
}
// Encrypts the given data with a random salt
static Uint8List en(Uint8List data) {
if (data.isEmpty) return data;
final salt = randSalt();
final encryptedData = enWithSalt(data, salt);
return Uint8List.fromList([salt.length, ...salt, ...encryptedData]);
}
// Decrypts the given encrypted data
static Uint8List de(Uint8List data) {
if (data.isEmpty) return data;
final saltLen = data[0];
final salt = data.sublist(1, 1 + saltLen);
final encrypted = data.sublist(1 + saltLen);
return deWithSalt(encrypted, salt);
}
// Encrypts data with a specified salt
static Uint8List enWithSalt(Uint8List data, Uint8List salt) {
final mixedData = mixSalt(data, salt);
return cxEd(mixedData);
}
// Decrypts data with a specified salt
static Uint8List deWithSalt(Uint8List data, Uint8List salt) {
final decryptedData = cxEd(data);
return removeSalt(decryptedData, salt);
}
// Simple XOR-based encryption/decryption (bitwise negation)
static Uint8List cxEd(Uint8List data) {
return Uint8List.fromList(data.map((b) => b ^ 0xFF).toList());
}
// Apply salt to the data for encryption
static Uint8List mixSalt(Uint8List data, Uint8List salt) {
if (salt.isEmpty) return data;
return Uint8List.fromList(
List.generate(data.length, (i) {
final s = salt[i % salt.length];
return calSalt(data[i], s);
}),
);
}
// Remove salt from the data after decryption
static Uint8List removeSalt(Uint8List data, Uint8List salt) {
if (salt.isEmpty) return data;
return Uint8List.fromList(
List.generate(data.length, (i) {
final s = salt[i % salt.length];
return calRemoveSalt(data[i], s);
}),
);
}
// Calculate a modified value after mixing salt (for encryption)
static int calSalt(int v, int s) {
final r = v ^ 0xFF;
return s > r ? (s - r - 1) & 0xFF : (v + s) & 0xFF;
}
// Reverse the salt mixing process for decryption
static int calRemoveSalt(int v, int s) {
return v >= s ? (v - s) & 0xFF : (0xFF - (s - v) + 1) & 0xFF;
}
// Encrypt a string and return the hex representation
static String cxEStr(String data) {
return cxEStrAsBytes(
data,
).map((b) => b.toRadixString(16).padLeft(2, '0')).join();
}
// Encrypt string and return as bytes
static Uint8List cxEStrAsBytes(String data) {
return cxEd(Uint8List.fromList(utf8.encode(data)));
}
// Decrypt a hex string to a regular string
static String cxDStr(String data) {
final bytes = Uint8List.fromList([
for (int i = 0; i < data.length; i += 2)
int.parse(data.substring(i, i + 2), radix: 16),
]);
return utf8.decode(cxEd(bytes));
}
// Encrypt string data and return in the form of hex string with prefix
static String enStr(String data) {
return enBytesStr(Uint8List.fromList(utf8.encode(data)));
}
// Encrypt bytes and return as hex string with prefix
static String enBytesStr(Uint8List data) {
final encrypted = en(data);
return '$enStrTag${encrypted.map((b) => b.toRadixString(16).padLeft(2, '0')).join()}';
}
// Decrypt a string with encoded prefix (e.g., "$" symbol)
static String deStr(String data) {
return utf8.decode(deStrBytes(data));
}
// Decrypt the byte array extracted from a string
static Uint8List deStrBytes(String data) {
if (!data.startsWith(enStrTag)) {
throw ArgumentError("Invalid encoded string");
}
final hexData = data.substring(1);
final bytes = Uint8List.fromList([
for (int i = 0; i < hexData.length; i += 2)
int.parse(hexData.substring(i, i + 2), radix: 16),
]);
return de(bytes);
}
// Copy encrypted data from a stream to another stream
static Future<int> copy(
Stream<List<int>> reader,
StreamSink<List<int>> writer,
) async {
int total = 0;
await for (final chunk in reader) {
total += chunk.length;
final encrypted = chunk.map((b) => b ^ 0xFF).toList();
writer.add(encrypted);
}
await writer.close();
return total;
}
// Custom encryption with condition (even and odd byte values handled differently)
static Uint8List cxEd1(Uint8List data) {
return Uint8List.fromList(
data.map((b) => (b % 2 == 1) ? (b ^ 0xFF) : b).toList(),
);
}
}

View File

@ -45,7 +45,9 @@ class ConstImage {
static const String settings = "assets/other/settings.png";
// copy
static const String copy = "assets/other/copy.png";
// pricary web_site
static const String primary = "assets/other/pricary.png";
static const String webSite = "assets/other/web_site.png";
}

View File

@ -0,0 +1,146 @@
import 'dart:convert';
import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:talker_flutter/talker_flutter.dart';
class HelpLog {
HelpLog._();
static final HelpLog _instance = HelpLog._();
static HelpLog get instance => _instance;
final Talker _talker = TalkerFlutter.init();
factory HelpLog() => _instance;
Talker get tLog => _talker;
/// DevTools /
static const String logName = 'HelpLog';
Future<void> initLogger() async {
/// app
}
/// [kDebugMode] JSON
static void print(
Object? message, {
String? tag,
String name = logName,
}) {
if (!kDebugMode) return;
try {
final text = _stringify(message);
final line = tag != null ? '[debug-tag: $tag] ===== $text' : text;
log(line, name: name);
} catch (e, st) {
log(
'[SkyLog.print failed] $e',
name: name,
error: e,
stackTrace: st,
);
}
}
/// JSON退 [toString]
static String _stringify(Object? value) {
if (value == null) return 'null';
if (value is String) return value;
if (value is num || value is bool) return value.toString();
const encoder = JsonEncoder.withIndent(' ');
if (value is Map) {
try {
return encoder.convert(value);
} catch (_) {
return value.toString();
}
}
if (value is List) {
try {
return encoder.convert(value);
} catch (_) {
return value.toString();
}
}
if (value is Iterable) {
try {
return encoder.convert(value.toList());
} catch (_) {
return value.toString();
}
}
try {
return encoder.convert(value);
} catch (_) {
try {
return '${value.runtimeType}: $value';
} catch (_) {
return '<unprintable ${value.runtimeType}>';
}
}
}
/// Talker Talker Debug [log]
static void error(
Object? message, {
String? tag,
StackTrace? stackTrace,
}) {
final text = message == null ? 'null' : '$message';
try {
_instance._talker.logCustom(
CustomLog(
text,
settings: _instance._talker.settings,
tag: tag,
logLevel: LogLevel.error,
stackTrace: stackTrace,
),
);
} catch (e, st) {
if (kDebugMode) {
log(
'[SkyLog.error fallback] $text | $e',
name: logName,
error: e,
stackTrace: st,
);
}
}
}
}
class CustomLog extends TalkerLog {
final String? tag;
final TalkerSettings settings;
final LogLevel? _logLevel;
final StackTrace? _stackTrace;
CustomLog(
super.message, {
this.tag,
required this.settings,
LogLevel logLevel = LogLevel.debug,
StackTrace? stackTrace,
}) : _logLevel = logLevel,
_stackTrace = stackTrace;
@override
String? get key => tag;
@override
LogLevel? get logLevel => _logLevel;
@override
StackTrace? get stackTrace => _stackTrace;
@override
AnsiPen? get pen =>
settings.colors[TalkerKey.fromLogLevel(logLevel ?? LogLevel.info)];
}

View File

@ -0,0 +1,55 @@
class HelpTime {
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 unit = 'k',
}) {
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) {
int mu = unit=='w' ? 10000 : 1000;
final formatted = (value / mu).toStringAsFixed(decimalDigits);
return '${removeTrailingZeros(formatted)}$unit';
} else {
final formatted = (value / 1000000).toStringAsFixed(decimalDigits);
return '${removeTrailingZeros(formatted)}m';
}
}
}

View File

@ -0,0 +1,228 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:novyronst/common/api/user_request.dart';
import 'package:shared_preferences/shared_preferences.dart';
class HelpToken {
static final HelpToken _instance = HelpToken._internal();
factory HelpToken() => _instance;
HelpToken._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 UserRequest.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');
}
}
}

View File

@ -0,0 +1,60 @@
import 'package:novyronst/common/model/parse_model.dart';
class NovyronstUser {
final String customerId;
final bool isTourist;
final String userLevel;
final String avator;
final String? familyName;
final String? givingName;
const NovyronstUser({
required this.customerId,
required this.isTourist,
required this.userLevel,
required this.avator,
this.familyName,
this.givingName,
});
factory NovyronstUser.fromJson(Map<String, dynamic> json) {
return NovyronstUser(
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,
};
}
NovyronstUser copyWith({
String? customerId,
bool? isTourist,
String? userLevel,
String? avator,
String? familyName,
String? givingName,
}) {
return NovyronstUser(
customerId: customerId ?? this.customerId,
isTourist: isTourist ?? this.isTourist,
userLevel: userLevel ?? this.userLevel,
avator: avator ?? this.avator,
familyName: familyName ?? this.familyName,
givingName: givingName ?? this.givingName,
);
}
}

View File

@ -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();
}

View File

@ -0,0 +1,18 @@
class RegisterModel {
final String token;
final int customerId;
final bool? autoLogin;
final int touristId;
RegisterModel({required this.token, required this.customerId, this.autoLogin, required this.touristId});
factory RegisterModel.fromJson(Map<String, dynamic> json) {
return RegisterModel(
token: json['token'],
customerId: json['customer_id'],
autoLogin: json['auto_login'],
touristId: json['tourist_id'],
);
}
}

View File

@ -3,6 +3,7 @@ import 'package:novyronst/common/help/help_image.dart';
import 'package:novyronst/pages/my/widgets/ny_dash_line.dart';
import 'package:novyronst/root/lib_export.dart';
import 'package:novyronst/tools/widgets/app_bg_page.dart';
import 'package:novyronst/tools/widgets/extend_widget.dart';
import 'package:novyronst/tools/widgets/novy_com_widget.dart';
class MyPage extends StatefulWidget {
@ -16,6 +17,10 @@ class MyPage extends StatefulWidget {
class _RootPageState extends State<MyPage> {
List<String> _titles = [
'History', 'Settings', 'About', 'Privacy Policy', 'User Agreement'
];
@override
Widget build(BuildContext context) {
return AppBgPage(
@ -26,13 +31,65 @@ class _RootPageState extends State<MyPage> {
children: [
SizedBox(height: AppScreen.statusBarHeight + 48),
_buildUserInfo(),
NyDashLine()
const SizedBox(height: 26),
...List.generate(_titles.length, (index) {
return _buildSectionView(_titles[index], index, onTap: () {
if (index == 0) {
} else if (index == 1) {
} else if (index == 2) {
} else if (index == 3) {
} else if (index == 4) {
}
});
})
],
),
),
);
}
Widget _buildSectionView(String title, int index, {Function()? onTap}) {
String imgName = ConstImage.history;
if (index == 1) {
imgName = ConstImage.settings;
} else if (index == 2) {
imgName = ConstImage.about;
} else if (index == 3) {
imgName = ConstImage.primary;
} else if (index == 4) {
imgName = ConstImage.webSite;
}
return Container(
height: 60,
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Column(
children: [
Expanded(child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
HelpImage.asset(imgName, width: 36, height: 36),
const SizedBox(width: 10),
NovyText(title,
fontSize: 14,
)
],
),
Icon(Icons.arrow_forward_ios, size: 17, color: Colors.white)
],
)),
if (index < 4) NyDashLine()
],
),
).addInkWell(onTap: onTap);
}
Widget _buildUserInfo() {
return Row(
children: [

View File

@ -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:novyronst/common/api/api_string.dart';
import 'package:package_info_plus/package_info_plus.dart';
@immutable
class NyDeviceInfo {
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 NyDeviceInfo? _instance;
const NyDeviceInfo._({
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 NyDeviceInfo 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 = NyDeviceInfo._(
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 = ApiString.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 = NyDeviceInfo._(
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,
};
}
}

View File

@ -0,0 +1,139 @@
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:novyronst/common/help/help_image.dart';
//
typedef LoadBlock<T> = Future<T> Function();
// Toast提示的类
class AppToastUtil {
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: Image.asset(ConstImage.loading, fit: BoxFit.fill),
);
},
);
}
///
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();
}
}
}

View File

@ -9,6 +9,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.5.1"
ansicolor:
dependency: transitive
description:
name: ansicolor
sha256: "50e982d500bc863e1d703448afdbf9e5a72eb48840a4f766fa361ffd6877055f"
url: "https://pub.dev"
source: hosted
version: "2.0.3"
args:
dependency: transitive
description:
@ -65,6 +73,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.19.1"
cross_file:
dependency: transitive
description:
name: cross_file
sha256: "92c9c43c383bfa1c32079d3bc492d55d6d4318044b7b47edaff8971cbb555c51"
url: "https://pub.dev"
source: hosted
version: "0.3.5+4"
crypto:
dependency: transitive
description:
@ -137,6 +153,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "7.0.1"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
url: "https://pub.dev"
source: hosted
version: "1.1.1"
flutter:
dependency: "direct main"
description: flutter
@ -240,6 +264,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.1"
group_button:
dependency: transitive
description:
name: group_button
sha256: "0610fcf28ed122bfb4b410fce161a390f7f2531d55d1d65c5375982001415940"
url: "https://pub.dev"
source: hosted
version: "5.3.4"
hooks:
dependency: transitive
description:
@ -320,6 +352,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.1.0"
logger:
dependency: "direct main"
description:
name: logger
sha256: "25aee487596a6257655a1e091ec2ae66bc30e7af663592cc3a27e6591e05035c"
url: "https://pub.dev"
source: hosted
version: "2.7.0"
logging:
dependency: transitive
description:
@ -480,6 +520,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.6.0"
share_plus:
dependency: transitive
description:
name: share_plus
sha256: "223873d106614442ea6f20db5a038685cc5b32a2fba81cdecaefbbae0523f7fa"
url: "https://pub.dev"
source: hosted
version: "12.0.2"
share_plus_platform_interface:
dependency: transitive
description:
name: share_plus_platform_interface
sha256: "88023e53a13429bd65d8e85e11a9b484f49d4c190abbd96c7932b74d6927cc9a"
url: "https://pub.dev"
source: hosted
version: "6.1.0"
shared_preferences:
dependency: "direct main"
description:
@ -573,6 +629,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.4.1"
talker:
dependency: transitive
description:
name: talker
sha256: f1a14d623f1d1bec42bb3bb77674eb766ffe8d26e5f79af652d85cb097c3e757
url: "https://pub.dev"
source: hosted
version: "5.1.17"
talker_flutter:
dependency: "direct dev"
description:
name: talker_flutter
sha256: "54cbbf852101721664faf4a05639fd2fdefdc37178327990abea00390690d4bc"
url: "https://pub.dev"
source: hosted
version: "5.1.16"
talker_logger:
dependency: transitive
description:
name: talker_logger
sha256: "459205c3e571f97ecc6be6e1b1b7e6b97b853e78ea458894650be407596e3216"
url: "https://pub.dev"
source: hosted
version: "5.1.17"
term_glyph:
dependency: transitive
description:
@ -597,6 +677,46 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.4.0"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a
url: "https://pub.dev"
source: hosted
version: "3.2.2"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34"
url: "https://pub.dev"
source: hosted
version: "2.4.3"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f"
url: "https://pub.dev"
source: hosted
version: "3.1.5"
uuid:
dependency: transitive
description:
name: uuid
sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489"
url: "https://pub.dev"
source: hosted
version: "4.5.3"
vector_math:
dependency: transitive
description:

View File

@ -29,6 +29,7 @@ dependencies:
flutter_smart_dialog: ^5.1.0
flutter_secure_storage: ^9.2.4
android_id: ^0.5.1
logger: ^2.5.0
@ -36,8 +37,8 @@ dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^6.0.0
talker_flutter: ^5.0.2
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec