From ba8f306dbb825abcfef91196643c1e076461eceb Mon Sep 17 00:00:00 2001 From: Jolie <412895109@qq.com> Date: Tue, 4 Nov 2025 00:10:16 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=BD=91=E7=BB=9C=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E6=A1=86=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/images/login_logo.png | Bin 0 -> 30676 bytes ios/Flutter/Debug.xcconfig | 1 + ios/Flutter/Release.xcconfig | 1 + lib/config/env_config.dart | 49 +++ lib/generated/assets.dart | 7 + lib/main.dart | 165 +++---- lib/network/api_service.dart | 20 + lib/network/api_service.g.dart | 96 ++++ lib/network/index.dart | 4 + lib/network/network_config.dart | 169 +++++++ lib/network/network_service.dart | 189 ++++++++ lib/network/response_model.dart | 92 ++++ lib/network/user_api.dart | 17 + lib/network/user_api.g.dart | 97 ++++ lib/pages/main_page.dart | 15 + lib/pages/mine/login_page.dart | 15 + pubspec.lock | 732 ++++++++++++++++++++++++++++++- pubspec.yaml | 22 +- 18 files changed, 1579 insertions(+), 112 deletions(-) create mode 100644 assets/images/login_logo.png create mode 100644 lib/config/env_config.dart create mode 100644 lib/generated/assets.dart create mode 100644 lib/network/api_service.dart create mode 100644 lib/network/api_service.g.dart create mode 100644 lib/network/index.dart create mode 100644 lib/network/network_config.dart create mode 100644 lib/network/network_service.dart create mode 100644 lib/network/response_model.dart create mode 100644 lib/network/user_api.dart create mode 100644 lib/network/user_api.g.dart create mode 100644 lib/pages/main_page.dart create mode 100644 lib/pages/mine/login_page.dart diff --git a/assets/images/login_logo.png b/assets/images/login_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..8c2e9d6cc78840fbb4d285c29e48d6b1d5de56d7 GIT binary patch literal 30676 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

YEK`{nAAU5)6>hcXmu1h`*AAV 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 literal 0 HcmV?d00001 diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig index 592ceee..ec97fc6 100644 --- a/ios/Flutter/Debug.xcconfig +++ b/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig index 592ceee..c4855bf 100644 --- a/ios/Flutter/Release.xcconfig +++ b/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/lib/config/env_config.dart b/lib/config/env_config.dart new file mode 100644 index 0000000..332cbe1 --- /dev/null +++ b/lib/config/env_config.dart @@ -0,0 +1,49 @@ +import 'package:flutter/foundation.dart'; + +/// 环境类型枚举 +enum Environment { dev, release } + +/// 环境配置管理类 +class EnvConfig { + // 当前环境 + static Environment current = Environment.dev; // 默认开发环境 + + // 开发环境配置 + static const Map _devConfig = { + 'mainBaseUrl': 'https://dating-agency-api-test.qniao.cn', + 'fileBaseUrl': 'https://dating-agency-api-test.qniao.cn', + }; + + // 生产环境配置 + static const Map _releaseConfig = { + 'mainBaseUrl': 'https://dating-agency-api-test.qniao.cn', + 'fileBaseUrl': 'https://dating-agency-api-test.qniao.cn', + }; + + /// 获取当前环境的配置值 + static String get(String key) { + return (current == Environment.dev + ? _devConfig[key] + : _releaseConfig[key]) ?? + ''; + } + + /// 获取主API的baseUrl + static String get mainBaseUrl { + return current == Environment.dev + ? _devConfig['mainBaseUrl']! + : _releaseConfig['mainBaseUrl']!; + } + + /// 获取文件API的baseUrl + static String get fileBaseUrl { + return current == Environment.dev + ? _devConfig['fileBaseUrl']! + : _releaseConfig['fileBaseUrl']!; + } + + /// 设置环境(主要用于测试或特定场景) + static void setEnvironment(Environment env) { + current = env; + } +} diff --git a/lib/generated/assets.dart b/lib/generated/assets.dart new file mode 100644 index 0000000..f252c85 --- /dev/null +++ b/lib/generated/assets.dart @@ -0,0 +1,7 @@ +///This file is automatically generated. DO NOT EDIT, all your changes would be lost. +class Assets { + Assets._(); + + static const String imagesLoginLogo = 'assets/images/login_logo.png'; + +} diff --git a/lib/main.dart b/lib/main.dart index 7b7f5b6..cfecb75 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,122 +1,71 @@ +import 'dart:io'; + +import 'package:dating_touchme_app/config/env_config.dart'; +import 'package:dating_touchme_app/pages/main_page.dart'; +import 'package:dating_touchme_app/pages/mine/login_page.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.dart'; +import 'package:get_storage/get_storage.dart'; -void main() { - runApp(const MyApp()); -} +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + // 初始化GetStorage + await GetStorage.init(); -class MyApp extends StatelessWidget { - const MyApp({super.key}); + // 设置环境配置 - 根据是否为release模式 + EnvConfig.setEnvironment(Environment.dev); - // This widget is the root of your application. - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - // This is the theme of your application. - // - // TRY THIS: Try running your application with "flutter run". You'll see - // the application has a purple toolbar. Then, without quitting the app, - // try changing the seedColor in the colorScheme below to Colors.green - // and then invoke "hot reload" (save your changes or press the "hot - // reload" button in a Flutter-supported IDE, or press "r" if you used - // the command line to start the app). - // - // Notice that the counter didn't reset back to zero; the application - // state is not lost during the reload. To reset the state, use hot - // restart instead. - // - // This works for code too, not just values: Most code changes can be - // tested with just a hot reload. - colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), - ), - home: const MyHomePage(title: 'Flutter Demo Home Page'), + SystemChrome.setSystemUIOverlayStyle( + const SystemUiOverlayStyle( + statusBarColor: Colors.transparent, + statusBarIconBrightness: Brightness.dark, + statusBarBrightness: Brightness.light, + systemNavigationBarColor: Colors.white, + systemNavigationBarIconBrightness: Brightness.dark, + ), + ); + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); + + if (Platform.isIOS) { + SystemChrome.setEnabledSystemUIMode( + SystemUiMode.manual, + overlays: [SystemUiOverlay.top], ); } -} - -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); + runApp( + GetMaterialApp( + locale: Get.locale, // 使用GetX的locale + supportedLocales: const [Locale('zh', 'CN')], - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". - - final String title; - - @override - State createState() => _MyHomePageState(); + /// 国际化配置 代理 + localizationsDelegates: const [ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, // iOS + ], + builder: FlutterSmartDialog.init(), + home: MyApp(), + ), + ); } -class _MyHomePageState extends State { - int _counter = 0; - - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); - } +class MyApp extends StatelessWidget { + const MyApp({super.key}); + // This widget is the root of your application. @override Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. - return Scaffold( - appBar: AppBar( - // TRY THIS: Try changing the color here to a specific color (to - // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar - // change color while the other colors stay the same. - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - // - // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint" - // action in the IDE, or press "p" in the console), to see the - // wireframe for each widget. - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('You have pushed the button this many times:'), - Text( - '$_counter', - style: Theme.of(context).textTheme.headlineMedium, - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.add), - ), // This trailing comma makes auto-formatting nicer for build methods. - ); + ScreenUtil.init(context, designSize: const Size(375, 812)); + + // 判断token是否为空 + final storage = GetStorage(); + final token = storage.read('token'); + + // 如果token为空,显示登录页面,否则显示主页 + return token == null || token.isEmpty ? const LoginPage() : MainPage(); } } diff --git a/lib/network/api_service.dart b/lib/network/api_service.dart new file mode 100644 index 0000000..2206dc8 --- /dev/null +++ b/lib/network/api_service.dart @@ -0,0 +1,20 @@ +import 'package:retrofit/retrofit.dart'; +import 'package:dio/dio.dart'; + +part 'api_service.g.dart'; + +@RestApi(baseUrl: 'https://api.example.com/') +abstract class ApiService { + factory ApiService(Dio dio) = _ApiService; + + @GET("{path}") + Future> get( + @Path("path") String path, + ); + + @POST("{path}") + Future> post( + @Path("path") String path, + @Body() dynamic data, + ); +} \ No newline at end of file diff --git a/lib/network/api_service.g.dart b/lib/network/api_service.g.dart new file mode 100644 index 0000000..bfee117 --- /dev/null +++ b/lib/network/api_service.g.dart @@ -0,0 +1,96 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'api_service.dart'; + +// dart format off + +// ************************************************************************** +// RetrofitGenerator +// ************************************************************************** + +// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations,unused_element_parameter,avoid_unused_constructor_parameters,unreachable_from_main + +class _ApiService implements ApiService { + _ApiService(this._dio, {this.baseUrl, this.errorLogger}) { + baseUrl ??= 'https://api.example.com/'; + } + + final Dio _dio; + + String? baseUrl; + + final ParseErrorLogger? errorLogger; + + @override + Future> get(String path) async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + const Map? _data = null; + final _options = _setStreamType>( + Options(method: 'GET', headers: _headers, extra: _extra) + .compose( + _dio.options, + '${path}', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); + final _result = await _dio.fetch(_options); + final _value = _result.data; + final httpResponse = HttpResponse(_value, _result); + return httpResponse; + } + + @override + Future> post(String path, dynamic data) async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + final _data = data; + final _options = _setStreamType>( + Options(method: 'POST', headers: _headers, extra: _extra) + .compose( + _dio.options, + '${path}', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); + final _result = await _dio.fetch(_options); + final _value = _result.data; + final httpResponse = HttpResponse(_value, _result); + return httpResponse; + } + + RequestOptions _setStreamType(RequestOptions requestOptions) { + if (T != dynamic && + !(requestOptions.responseType == ResponseType.bytes || + requestOptions.responseType == ResponseType.stream)) { + if (T == String) { + requestOptions.responseType = ResponseType.plain; + } else { + requestOptions.responseType = ResponseType.json; + } + } + return requestOptions; + } + + String _combineBaseUrls(String dioBaseUrl, String? baseUrl) { + if (baseUrl == null || baseUrl.trim().isEmpty) { + return dioBaseUrl; + } + + final url = Uri.parse(baseUrl); + + if (url.isAbsolute) { + return url.toString(); + } + + return Uri.parse(dioBaseUrl).resolveUri(url).toString(); + } +} + +// dart format on diff --git a/lib/network/index.dart b/lib/network/index.dart new file mode 100644 index 0000000..cd5873c --- /dev/null +++ b/lib/network/index.dart @@ -0,0 +1,4 @@ +export 'api_service.dart'; +export 'network_config.dart'; +export 'network_service.dart'; +export 'response_model.dart'; \ No newline at end of file diff --git a/lib/network/network_config.dart b/lib/network/network_config.dart new file mode 100644 index 0000000..ba0ef7a --- /dev/null +++ b/lib/network/network_config.dart @@ -0,0 +1,169 @@ +import 'package:dio/dio.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.dart' hide Response; +import 'package:get_storage/get_storage.dart'; + +import '../config/env_config.dart'; +import '../pages/mine/login_page.dart'; + +/// API类型枚举,用于区分不同的baseUrl +enum ApiType { + main, // 主API + file, // 文件上传下载API +} + +/// 网络请求配置类 +class NetworkConfig { + static const int connectTimeout = 30000; + static const int receiveTimeout = 30000; + + /// 根据API类型创建不同的Dio实例 + static Dio createDio({ApiType type = ApiType.main}) { + // 根据类型选择对应的baseUrl + String currentBaseUrl; + switch (type) { + case ApiType.main: + currentBaseUrl = EnvConfig.mainBaseUrl; + break; + case ApiType.file: + currentBaseUrl = EnvConfig.fileBaseUrl; + break; + } + + final dio = Dio( + BaseOptions( + baseUrl: currentBaseUrl, + connectTimeout: Duration(milliseconds: connectTimeout), + receiveTimeout: Duration(milliseconds: receiveTimeout), + contentType: 'application/json', + responseType: ResponseType.json, + ), + ); + + // 添加请求拦截器 + dio.interceptors.add(AuthInterceptor()); + + // 添加响应拦截器 + dio.interceptors.add(ResponseInterceptor()); + + // 添加日志拦截器 + dio.interceptors.add(LogInterceptor( + requestBody: true, + responseBody: true, + error: true, + )); + + return dio; + } +} + +/// 请求拦截器 +class AuthInterceptor extends Interceptor { + @override + void onRequest( + RequestOptions options, + RequestInterceptorHandler handler, + ) async { + // 添加token等认证信息 + final token = _getToken(); + if (token != null && token.isNotEmpty) { + options.headers['Authorization'] = 'Bearer $token'; + } + + // 添加其他通用请求头 + options.headers['X-APP-ID'] = ''; + + handler.next(options); + } + + String? _getToken() { + // 从本地存储获取token + // 这里可以使用get_storage或其他存储方式 + return GetStorage().read('token'); + } +} + +/// 响应拦截器 +class ResponseInterceptor extends Interceptor { + @override + void onResponse( + Response response, + ResponseInterceptorHandler handler, + ) { + // 统一处理响应数据 + final data = response.data; + + // 检查响应状态码 + if (response.statusCode == 200) { + // 假设后端返回的数据格式为 {"code": 0, "message": "success", "data": {...}} + if (data is Map) { + final code = data['code'] ?? 0; + if (code == 0) { + // 请求成功 + handler.next(response); + } else { + // 业务错误 + final message = data['message'] ?? '请求失败'; + _showError(message); + handler.reject(DioException( + requestOptions: response.requestOptions, + error: message, + type: DioExceptionType.badResponse, + )); + } + } else { + handler.next(response); + } + } else { + handler.next(response); + } + } + + @override + void onError(DioException err, ErrorInterceptorHandler handler) { + // 统一处理错误 + String errorMessage = '网络请求失败'; + + switch (err.type) { + case DioExceptionType.connectionTimeout: + case DioExceptionType.sendTimeout: + case DioExceptionType.receiveTimeout: + errorMessage = '网络连接超时,请检查网络'; + break; + case DioExceptionType.badResponse: + if (err.response?.statusCode == 401) { + errorMessage = '登录已过期,请重新登录'; + _handleTokenExpired(); + } else if (err.response?.statusCode == 403) { + errorMessage = '没有权限访问该资源'; + } else if (err.response?.statusCode == 404) { + errorMessage = '请求的资源不存在'; + } else if (err.response?.statusCode == 500) { + errorMessage = '服务器内部错误'; + } else { + errorMessage = '请求失败,状态码:${err.response?.statusCode}'; + } + break; + case DioExceptionType.cancel: + errorMessage = '请求已取消'; + break; + default: + errorMessage = err.message ?? '未知错误'; + } + + _showError(errorMessage); + handler.reject(err); + } + + void _showError(String message) { + // 使用FlutterSmartDialog显示错误信息 + SmartDialog.showToast(message, displayTime: const Duration(seconds: 2)); + } + + void _handleTokenExpired() { + // 处理token过期逻辑,如清除本地数据、跳转登录页等 + // 这里可以使用Get.offAllNamed('/login')等方式跳转 + GetStorage().remove('token'); + Get.offAll(() => LoginPage()); + } +} \ No newline at end of file diff --git a/lib/network/network_service.dart b/lib/network/network_service.dart new file mode 100644 index 0000000..59264b2 --- /dev/null +++ b/lib/network/network_service.dart @@ -0,0 +1,189 @@ +import 'package:dio/dio.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:retrofit/retrofit.dart'; + +import 'api_service.dart'; +import 'network_config.dart'; +import 'user_api.dart'; + +/// 网络请求服务管理器 +class NetworkService { + static final NetworkService _instance = NetworkService._internal(); + + // API服务实例 + late final ApiService _apiService; // 主API服务 + late final ApiService _fileApiService; // 文件API服务 + late final UserApi _userApi; + + /// 获取单例实例 + factory NetworkService() { + return _instance; + } + + NetworkService._internal() { + // 初始化API服务 - 使用主API + final dio = NetworkConfig.createDio(); + _apiService = ApiService(dio); + _userApi = UserApi(dio); + + // 初始化文件上传API服务 + final fileDio = NetworkConfig.createDio(type: ApiType.file); + _fileApiService = ApiService(fileDio); + + + } + + /// 通用GET请求 + Future get( + String path, + {Map? queryParameters, + bool showLoading = false, + bool showError = true, + ApiType apiType = ApiType.main}) async { + try { + if (showLoading) { + _showLoading(); + } + + // 如果有queryParameters,将其附加到path + String finalPath = path; + if (queryParameters != null && queryParameters.isNotEmpty) { + final queryString = queryParameters.entries + .map((e) => '${e.key}=${e.value}') + .join('&'); + finalPath = '$path?$queryString'; + } + + // 根据API类型选择对应的服务 + final apiService = _getApiServiceByType(apiType); + final response = await apiService.get(finalPath); + + return response.data; + } catch (e) { + if (showError) { + _handleError(e); + } + return null; + } finally { + if (showLoading) { + _hideLoading(); + } + } + } + + /// 通用POST请求 + Future post( + String path, + dynamic data, + {bool showLoading = false, + bool showError = true, + ApiType apiType = ApiType.main}) async { + try { + if (showLoading) { + _showLoading(); + } + + // 根据API类型选择对应的服务 + final apiService = _getApiServiceByType(apiType); + final response = await apiService.post( + path, + data, + ); + + return response.data; + } catch (e) { + if (showError) { + _handleError(e); + } + return null; + } finally { + if (showLoading) { + _hideLoading(); + } + } + } + + /// 通用PUT请求(通过POST模拟) + Future put( + String path, + dynamic data, + {bool showLoading = false, + bool showError = true, + ApiType apiType = ApiType.main}) async { + // 注意:这是一个简化实现,实际项目中应该使用真实的PUT方法 + try { + if (showLoading) { + _showLoading(); + } + + // 在实际项目中,应该使用支持PUT方法的API服务 + // 这里使用POST作为替代 + final apiService = _getApiServiceByType(apiType); + final response = await apiService.post( + path, + data, + ); + + return response.data; + } catch (e) { + if (showError) { + _handleError(e); + } + return null; + } finally { + if (showLoading) { + _hideLoading(); + } + } + } + + /// 通用DELETE请求(通过GET模拟) + Future delete( + String path, + {bool showLoading = false, + bool showError = true, + ApiType apiType = ApiType.main}) async { + // 注意:这是一个简化实现,实际项目中应该使用真实的DELETE方法 + return get(path, showLoading: showLoading, showError: showError, apiType: apiType); + } + + /// 通用PATCH请求(通过POST模拟) + Future patch( + String path, + dynamic data, + {bool showLoading = false, + bool showError = true, + ApiType apiType = ApiType.main}) async { + // 注意:这是一个简化实现,实际项目中应该使用真实的PATCH方法 + return post(path, data, showLoading: showLoading, showError: showError, apiType: apiType); + } + + /// 根据API类型获取对应的API服务实例 + ApiService _getApiServiceByType(ApiType type) { + switch (type) { + case ApiType.main: + return _apiService; + case ApiType.file: + return _fileApiService; + } + } + + /// 用户相关API + UserApi get userApi => _userApi; + + void _showLoading() { + // 使用FlutterSmartDialog显示加载指示器 + SmartDialog.showLoading(); + } + + void _hideLoading() { + // 隐藏加载指示器 + SmartDialog.dismiss(); + } + + void _handleError(dynamic error) { + // 统一错误处理逻辑 + print('网络错误: $error'); + // 这里可以根据不同的错误类型进行处理 + } +} \ No newline at end of file diff --git a/lib/network/response_model.dart b/lib/network/response_model.dart new file mode 100644 index 0000000..43cfe1f --- /dev/null +++ b/lib/network/response_model.dart @@ -0,0 +1,92 @@ +/// API响应模型基类 +class BaseResponse { + final int code; + final String message; + final T? data; + + BaseResponse({ + required this.code, + required this.message, + this.data, + }); + + factory BaseResponse.fromJson(Map json, T Function(dynamic)? fromJsonT) { + return BaseResponse( + code: json['code'] ?? 0, + message: json['message'] ?? '', + data: fromJsonT != null && json.containsKey('data') && json['data'] != null + ? fromJsonT(json['data']) + : null, + ); + } + + Map toJson(Object? Function(T?)? toJsonT) { + final Map data = {}; + data['code'] = code; + data['message'] = message; + if (this.data != null && toJsonT != null) { + data['data'] = toJsonT(this.data); + } + return data; + } + + /// 是否请求成功 + bool get isSuccess => code == 0; + + @override + String toString() { + return 'BaseResponse{code: $code, message: $message, data: $data}'; + } +} + +/// 分页响应模型 +class PaginatedResponse { + final List list; + final int total; + final int page; + final int pageSize; + final bool hasMore; + + PaginatedResponse({ + required this.list, + required this.total, + required this.page, + required this.pageSize, + this.hasMore = false, + }); + + factory PaginatedResponse.fromJson( + Map json, + T Function(dynamic) fromJsonT, + ) { + final list = []; + if (json['list'] != null && json['list'] is List) { + for (final item in json['list']) { + list.add(fromJsonT(item)); + } + } + + return PaginatedResponse( + list: list, + total: json['total'] ?? 0, + page: json['page'] ?? 1, + pageSize: json['pageSize'] ?? 10, + hasMore: json['hasMore'] ?? false, + ); + } + + Map toJson(Object Function(T) toJsonT) { + return { + 'list': list.map((e) => toJsonT(e)).toList(), + 'total': total, + 'page': page, + 'pageSize': pageSize, + 'hasMore': hasMore, + }; + } + + @override + String toString() { + return 'PaginatedResponse{list: $list, total: $total, page: $page, pageSize: $pageSize, hasMore: $hasMore}'; + } +} \ No newline at end of file diff --git a/lib/network/user_api.dart b/lib/network/user_api.dart new file mode 100644 index 0000000..10be5c8 --- /dev/null +++ b/lib/network/user_api.dart @@ -0,0 +1,17 @@ +import 'package:retrofit/retrofit.dart'; +import 'package:dio/dio.dart'; + +part 'user_api.g.dart'; + +@RestApi(baseUrl: 'https://api.example.com/') +abstract class UserApi { + factory UserApi(Dio dio) = _UserApi; + + @POST("auth/login") + Future> login( + @Body() Map data, + ); + + @GET("user/info") + Future> getUserInfo(); +} \ No newline at end of file diff --git a/lib/network/user_api.g.dart b/lib/network/user_api.g.dart new file mode 100644 index 0000000..0889e19 --- /dev/null +++ b/lib/network/user_api.g.dart @@ -0,0 +1,97 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'user_api.dart'; + +// dart format off + +// ************************************************************************** +// RetrofitGenerator +// ************************************************************************** + +// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations,unused_element_parameter,avoid_unused_constructor_parameters,unreachable_from_main + +class _UserApi implements UserApi { + _UserApi(this._dio, {this.baseUrl, this.errorLogger}) { + baseUrl ??= 'https://api.example.com/'; + } + + final Dio _dio; + + String? baseUrl; + + final ParseErrorLogger? errorLogger; + + @override + Future> login(Map data) async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + final _data = {}; + _data.addAll(data); + final _options = _setStreamType>( + Options(method: 'POST', headers: _headers, extra: _extra) + .compose( + _dio.options, + 'auth/login', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); + final _result = await _dio.fetch(_options); + final _value = _result.data; + final httpResponse = HttpResponse(_value, _result); + return httpResponse; + } + + @override + Future> getUserInfo() async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + const Map? _data = null; + final _options = _setStreamType>( + Options(method: 'GET', headers: _headers, extra: _extra) + .compose( + _dio.options, + 'user/info', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); + final _result = await _dio.fetch(_options); + final _value = _result.data; + final httpResponse = HttpResponse(_value, _result); + return httpResponse; + } + + RequestOptions _setStreamType(RequestOptions requestOptions) { + if (T != dynamic && + !(requestOptions.responseType == ResponseType.bytes || + requestOptions.responseType == ResponseType.stream)) { + if (T == String) { + requestOptions.responseType = ResponseType.plain; + } else { + requestOptions.responseType = ResponseType.json; + } + } + return requestOptions; + } + + String _combineBaseUrls(String dioBaseUrl, String? baseUrl) { + if (baseUrl == null || baseUrl.trim().isEmpty) { + return dioBaseUrl; + } + + final url = Uri.parse(baseUrl); + + if (url.isAbsolute) { + return url.toString(); + } + + return Uri.parse(dioBaseUrl).resolveUri(url).toString(); + } +} + +// dart format on diff --git a/lib/pages/main_page.dart b/lib/pages/main_page.dart new file mode 100644 index 0000000..da31f52 --- /dev/null +++ b/lib/pages/main_page.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +class MainPage extends StatefulWidget { + const MainPage({super.key}); + + @override + State createState() => _MainPageState(); +} + +class _MainPageState extends State { + @override + Widget build(BuildContext context) { + return const Placeholder(); + } +} diff --git a/lib/pages/mine/login_page.dart b/lib/pages/mine/login_page.dart new file mode 100644 index 0000000..6087cae --- /dev/null +++ b/lib/pages/mine/login_page.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +class LoginPage extends StatefulWidget { + const LoginPage({super.key}); + + @override + State createState() => _LoginPageState(); +} + +class _LoginPageState extends State { + @override + Widget build(BuildContext context) { + return const Placeholder(); + } +} diff --git a/pubspec.lock b/pubspec.lock index 182dc93..d8c8d3e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,46 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: f0bb5d1648339c8308cc0b9838d8456b3cfe5c91f9dc1a735b4d003269e5da9a + url: "https://pub.flutter-io.cn" + source: hosted + version: "88.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "0b7b9c329d2879f8f05d6c05b32ee9ec025f39b077864bdb5ac9a7b63418a98f" + url: "https://pub.flutter-io.cn" + source: hosted + version: "8.1.1" + ansicolor: + dependency: transitive + description: + name: ansicolor + sha256: "50e982d500bc863e1d703448afdbf9e5a72eb48840a4f766fa361ffd6877055f" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.3" + archive: + dependency: transitive + description: + name: archive + sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.0.7" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.7.0" async: dependency: transitive description: @@ -17,6 +57,54 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" + build: + dependency: transitive + description: + name: build + sha256: dfb67ccc9a78c642193e0c2d94cb9e48c2c818b3178a86097d644acdcde6a8d9 + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.0.2" + build_config: + dependency: transitive + description: + name: build_config + sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.0" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: "409002f1adeea601018715d613115cfaf0e31f512cb80ae4534c79867ae2363d" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.1.0" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: a9461b8e586bf018dd4afd2e13b49b08c6a844a4b226c8d1d10f3a723cdd78c3 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.10.1" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: a30f0a0e38671e89a492c44d005b5545b830a961575bbd8336d42869ff71066d + url: "https://pub.flutter-io.cn" + source: hosted + version: "8.12.0" characters: dependency: transitive description: @@ -25,6 +113,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.4.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.4" clock: dependency: transitive description: @@ -33,6 +129,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.1.2" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: "11654819532ba94c34de52ff5feb52bd81cba1de00ef2ed622fd50295f9d4243" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.11.0" collection: dependency: transitive description: @@ -41,6 +145,38 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.2" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "942a4791cd385a68ccb3b32c71c427aba508a1bb949b86dff2adbe4049f16239" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.3.5" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.6" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.2" cupertino_icons: dependency: "direct main" description: @@ -49,6 +185,38 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.0.8" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: c87dfe3d56f183ffe9106a18aebc6db431fc7c98c31a54b952a77f3d54a85697 + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.2" + dio: + dependency: "direct main" + description: + name: dio + sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9 + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.9.0" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.1" + event_bus: + dependency: "direct main" + description: + name: event_bus + sha256: "1a55e97923769c286d295240048fc180e7b0768902c3c2e869fe059aafa15304" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.1" fake_async: dependency: transitive description: @@ -57,6 +225,62 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.4" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.flutter-io.cn" + source: hosted + version: "7.0.1" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.9.3+2" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: "88707a3bec4b988aaed3b4df5d7441ee4e987f20b286cddca5d6a8270cab23f2" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.9.4+5" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.6.2" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.9.3+4" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.1" flutter: dependency: "direct main" description: flutter @@ -70,11 +294,237 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "5.0.0" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_native_splash: + dependency: "direct main" + description: + name: flutter_native_splash + sha256: "4fb9f4113350d3a80841ce05ebf1976a36de622af7d19aca0ca9a9911c7ff002" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.7" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: "306f0596590e077338312f38837f595c04f28d6cdeeac392d3d74df2f0003687" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.32" + flutter_screenutil: + dependency: "direct main" + description: + name: flutter_screenutil + sha256: "8239210dd68bee6b0577aa4a090890342d04a136ce1c81f98ee513fc0ce891de" + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.9.3" + flutter_smart_dialog: + dependency: "direct main" + description: + name: flutter_smart_dialog + sha256: "0852df132cb03fd8fc5144eb404c31eb7eb50c22aecb1cc2504f2f598090d756" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.9.8+9" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.0.0" + get: + dependency: "direct main" + description: + name: get + sha256: c79eeb4339f1f3deffd9ec912f8a923834bec55f7b49c9e882b8fef2c139d425 + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.7.2" + get_storage: + dependency: "direct main" + description: + name: get_storage + sha256: "39db1fffe779d0c22b3a744376e86febe4ade43bf65e06eab5af707dc84185a2" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.1" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.3" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.3.2" + hotreloader: + dependency: transitive + description: + name: hotreloader + sha256: bc167a1163807b03bada490bfe2df25b0d744df359227880220a5cbd04e5734b + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.3.0" + html: + dependency: transitive + description: + name: html + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.15.6" + http: + dependency: transitive + description: + name: http + sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.5.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.1.2" + image: + dependency: transitive + description: + name: image + sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.5.4" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "736eb56a911cf24d1859315ad09ddec0b66104bc41a7f8c5b96b4e2620cf5041" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.0" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: ca2a3b04d34e76157e9ae680ef16014fb4c2d20484e78417eaed6139330056f6 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.8.13+7" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "40c2a6a0da15556dc0f8e38a3246064a971a9f512386c3339b89f76db87269b6" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.0" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: e675c22790bcc24e9abd455deead2b7a88de4b79f7327a281812f14de1a56f58 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.8.13+1" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.2.2" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.2.2+1" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.11.1" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.2.2" + intl: + dependency: "direct main" + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.20.2" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.5" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.9.0" + json_serializable: + dependency: "direct dev" + description: + name: json_serializable + sha256: "33a040668b31b320aafa4822b7b1e177e163fc3c1e835c6750319d4ab23aa6fe" + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.11.1" leak_tracker: dependency: transitive description: @@ -99,6 +549,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "3.0.2" + lean_builder: + dependency: transitive + description: + name: lean_builder + sha256: ef5cd5f907157eb7aa87d1704504b5a6386d2cbff88a3c2b3344477bab323ee9 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.1.2" lints: dependency: transitive description: @@ -107,6 +565,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "5.1.1" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.0" matcher: dependency: transitive description: @@ -131,6 +597,22 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.16.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.0" path: dependency: transitive description: @@ -139,11 +621,171 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.9.1" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: e122c5ea805bb6773bb12ce667611265980940145be920cd09a4b0ec0285cb16 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.20" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: efaec349ddfc181528345c56f8eda9d6cccd71c177511b132c6a0ddaefaa2738 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.3" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.3.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "7.0.1" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.8" + pool: + dependency: transitive + description: + name: pool + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.5.2" + posix: + dependency: transitive + description: + name: posix + sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61" + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.0.3" + protobuf: + dependency: transitive + description: + name: protobuf + sha256: "826d6a306be26f29e5cd9faeb0c97aad5897270341dab6dbd7b8acd675937006" + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.0.0" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.5.0" + retrofit: + dependency: "direct main" + description: + name: retrofit + sha256: "7d78824afa6eeeaf6ac58220910ee7a97597b39e93360d4bda230b7c6df45089" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.9.0" + retrofit_generator: + dependency: "direct dev" + description: + name: retrofit_generator + sha256: "47998fb9f214935e4eb00741aebc636ffcb62cb8ae73b474ac88127ce2744428" + url: "https://pub.flutter-io.cn" + source: hosted + version: "10.1.2" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.4.2" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.0" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.0" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "9098ab86015c4f1d8af6486b547b11100e73b193e1899015033cb3e14ad20243" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.0.2" + source_helper: + dependency: transitive + description: + name: source_helper + sha256: "6a3c6cc82073a8797f8c4dc4572146114a39652851c157db37e964d9c7038723" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.8" source_span: dependency: transitive description: @@ -168,6 +810,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.1.4" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.1" string_scanner: dependency: transitive description: @@ -192,6 +842,22 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "0.7.6" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.4.0" + universal_io: + dependency: transitive + description: + name: universal_io + sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.2" vector_math: dependency: transitive description: @@ -208,6 +874,70 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "15.0.2" + watcher: + dependency: transitive + description: + name: watcher + sha256: "592ab6e2892f67760543fb712ff0177f4ec76c031f02f5b4ff8d3fc5eb9fb61a" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.4" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.3" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.6.1" + xxh3: + dependency: transitive + description: + name: xxh3 + sha256: "399a0438f5d426785723c99da6b16e136f4953fb1e9db0bf270bd41dd4619916" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.3" sdks: dart: ">=3.9.0 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + flutter: ">=3.35.0" diff --git a/pubspec.yaml b/pubspec.yaml index 894ef1b..a1ffd83 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -30,10 +30,23 @@ environment: dependencies: flutter: sdk: flutter + flutter_localizations: + sdk: flutter # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 + get: ^4.7.2 + flutter_smart_dialog: ^4.9.7+8 + flutter_screenutil: ^5.9.3 + intl: ^0.20.2 + get_storage: ^2.1.1 + image_picker: ^1.1.1 + path_provider: ^2.1.4 + event_bus: ^2.0.1 + dio: ^5.9.0 + retrofit: ^4.9.0 + flutter_native_splash: ^2.4.7 dev_dependencies: flutter_test: @@ -45,6 +58,9 @@ dev_dependencies: # package. See that file for information about deactivating specific lint # rules and activating additional ones. flutter_lints: ^5.0.0 + build_runner: ^2.4.11 + json_serializable: ^6.7.1 + retrofit_generator: ^10.0.6 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec @@ -58,11 +74,11 @@ flutter: uses-material-design: true # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg + assets: + - assets/images/ # - images/a_dot_ham.jpeg - # An image asset can refer to one or more resolution-specific "variants", see + # An images asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/to/resolution-aware-images # For details regarding adding assets from package dependencies, see