From a7ac3b7fc792ef101b6616a1decf8ca9ce72d6b0 Mon Sep 17 00:00:00 2001 From: Zhou Weidong <502333560@qq.com> Date: Sat, 17 Oct 2026 02:30:16 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20A1=20=E7=BD=91=E7=BB=9C=E5=B1=82?= =?UTF-8?q?=E4=B8=8E=E9=89=B4=E6=9D=83=20+=20A2=20=E8=B4=A6=E5=8F=B7?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E5=AF=B9=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 引入 dio,封装统一 ApiClient(BaseUrl/Token 注入/401 自动刷新/{code,message,data} 解包) - TokenStorage 令牌持久化,AuthController 登录状态管理 - AuthRepository 封装 otp/login/logout 接口 - 登录页对接手机号/邮箱验证码登录(Google 登录待 OAuth client 配置) --- lib/app.dart | 15 ++ lib/core/config/api_config.dart | 15 ++ .../data/repositories/auth_repository.dart | 91 +++++++++ lib/core/data/services/api_client.dart | 136 +++++++++++++ lib/core/data/services/auth_controller.dart | 65 ++++++ lib/core/data/services/token_storage.dart | 34 ++++ lib/pages/login/login_view.dart | 133 +++++++++++- pubspec.lock | 190 +++++++++++------- pubspec.yaml | 1 + 9 files changed, 602 insertions(+), 78 deletions(-) create mode 100644 lib/core/config/api_config.dart create mode 100644 lib/core/data/repositories/auth_repository.dart create mode 100644 lib/core/data/services/api_client.dart create mode 100644 lib/core/data/services/auth_controller.dart create mode 100644 lib/core/data/services/token_storage.dart diff --git a/lib/app.dart b/lib/app.dart index 1e1b0cc..265e197 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -4,11 +4,15 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'core/config/locale_controller.dart'; +import 'core/data/repositories/auth_repository.dart'; import 'core/data/repositories/catalog_repository.dart'; import 'core/data/repositories/engagement_repository.dart'; +import 'core/data/services/api_client.dart'; +import 'core/data/services/auth_controller.dart'; import 'core/data/services/catalog_controller.dart'; import 'core/data/services/engagement_controller.dart'; import 'core/data/services/playback_session.dart'; +import 'core/data/services/token_storage.dart'; import 'core/data/services/video_player_port.dart'; import 'l10n/app_localizations.dart'; import 'pages/login/login_view.dart'; @@ -24,6 +28,17 @@ class LumaDramaApp extends StatelessWidget { providers: [ Provider(create: (_) => PlaybackCoordinator()), Provider(create: (_) => playerFactory), + Provider(create: (_) => ApiClient()), + Provider(create: (_) => TokenStorage()), + Provider( + create: (ctx) => AuthRepository( + ctx.read(), + ctx.read(), + ), + ), + ChangeNotifierProvider( + create: (ctx) => AuthController(ctx.read()), + ), ChangeNotifierProvider( create: (_) => CatalogController(DemoCatalogRepository())..load(), ), diff --git a/lib/core/config/api_config.dart b/lib/core/config/api_config.dart new file mode 100644 index 0000000..dece601 --- /dev/null +++ b/lib/core/config/api_config.dart @@ -0,0 +1,15 @@ +// Author: fruit.com.cn + +/// 接口地址与超时配置。 +class ApiConfig { + ApiConfig._(); + + /// 客户端 app-api 地址,可用 --dart-define=API_BASE_URL=... 覆盖。 + static const String baseUrl = String.fromEnvironment( + 'API_BASE_URL', + defaultValue: 'http://127.0.0.1:8081', + ); + + static const int connectTimeoutSeconds = 10; + static const int receiveTimeoutSeconds = 15; +} diff --git a/lib/core/data/repositories/auth_repository.dart b/lib/core/data/repositories/auth_repository.dart new file mode 100644 index 0000000..b233fdd --- /dev/null +++ b/lib/core/data/repositories/auth_repository.dart @@ -0,0 +1,91 @@ +// Author: fruit.com.cn + +import 'api_client.dart'; +import 'token_storage.dart'; + +/// 登录/刷新成功后返回的会话令牌。 +class AuthSession { + AuthSession({ + required this.accessToken, + required this.refreshToken, + required this.expiresIn, + required this.newUser, + }); + + final String accessToken; + final String refreshToken; + final int expiresIn; + final bool newUser; + + factory AuthSession.fromJson(Map json) { + return AuthSession( + accessToken: json['accessToken'] as String, + refreshToken: json['refreshToken'] as String, + expiresIn: (json['expiresIn'] as num?)?.toInt() ?? 7200, + newUser: json['newUser'] as bool? ?? false, + ); + } +} + +/// 账号与鉴权接口封装。 +class AuthRepository { + AuthRepository(this._client, this._tokenStorage); + + final ApiClient _client; + final TokenStorage _tokenStorage; + + /// 发送验证码。 + Future sendOtp({ + required String scene, + required String targetType, + required String target, + }) async { + await _client.post('/app/auth/otp', data: { + 'scene': scene, + 'targetType': targetType, + 'target': target, + }); + } + + /// 验证码登录。 + Future loginWithCode({ + required String targetType, + required String target, + required String code, + }) async { + final data = await _client.post('/app/auth/login', data: { + 'targetType': targetType, + 'target': target, + 'code': code, + }); + return AuthSession.fromJson(data as Map); + } + + /// 密码登录。 + Future loginWithPassword({ + required String targetType, + required String target, + required String password, + }) async { + final data = await _client.post('/app/auth/login', data: { + 'targetType': targetType, + 'target': target, + 'password': password, + }); + return AuthSession.fromJson(data as Map); + } + + /// 保存会话令牌。 + Future saveSession(AuthSession session) async { + await _tokenStorage.saveTokens( + access: session.accessToken, + refresh: session.refreshToken, + ); + } + + /// 登出并清理本地令牌。 + Future logout() async { + await _client.post('/app/auth/logout'); + await _tokenStorage.clear(); + } +} diff --git a/lib/core/data/services/api_client.dart b/lib/core/data/services/api_client.dart new file mode 100644 index 0000000..dc78f4a --- /dev/null +++ b/lib/core/data/services/api_client.dart @@ -0,0 +1,136 @@ +// Author: fruit.com.cn + +import 'package:dio/dio.dart'; + +import '../../config/api_config.dart'; +import 'token_storage.dart'; + +/// 统一业务异常:code 为后端业务码,message 可直接展示。 +class ApiException implements Exception { + ApiException(this.code, this.message); + + final int code; + final String message; + + @override + String toString() => 'ApiException($code): $message'; +} + +/// 统一 HTTP 客户端:BaseUrl、Token 注入、401 自动刷新、{code,message,data} 解包。 +class ApiClient { + ApiClient({Dio? dio, TokenStorage? tokenStorage}) + : _dio = dio ?? Dio(), + _tokenStorage = tokenStorage ?? TokenStorage() { + _dio.options + ..baseUrl = ApiConfig.baseUrl + ..connectTimeout = const Duration( + seconds: ApiConfig.connectTimeoutSeconds, + ) + ..receiveTimeout = const Duration( + seconds: ApiConfig.receiveTimeoutSeconds, + ); + _dio.interceptors.add( + InterceptorsWrapper(onRequest: _onRequest, onError: _onError), + ); + } + + final Dio _dio; + final TokenStorage _tokenStorage; + + bool _refreshing = false; + + Future _onRequest( + RequestOptions options, + RequestInterceptorHandler handler, + ) async { + final access = await _tokenStorage.readAccessToken(); + if (access != null && access.isNotEmpty) { + options.headers['Authorization'] = 'Bearer $access'; + } + // 设备标识:第一阶段固定占位,A2 接入真实 deviceId。 + options.headers['X-Device-Id'] = 'device-0001'; + handler.next(options); + } + + Future _onError( + DioException error, + ErrorInterceptorHandler handler, + ) async { + final status = error.response?.statusCode; + final path = error.requestOptions.path; + final isAuthEndpoint = + path.endsWith('/auth/refresh') || path.endsWith('/auth/login'); + if (status == 401 && !isAuthEndpoint && !_refreshing) { + final refreshed = await _refresh(); + if (refreshed) { + handler.resolve(await _retry(error.requestOptions)); + return; + } + } + handler.next(error); + } + + Future _refresh() async { + _refreshing = true; + try { + final refresh = await _tokenStorage.readRefreshToken(); + if (refresh == null || refresh.isEmpty) return false; + final response = await _dio.post( + '/app/auth/refresh', + data: {'refreshToken': refresh}, + ); + final body = response.data; + if (body is Map && body['code'] == 0) { + final data = body['data'] as Map; + await _tokenStorage.saveTokens( + access: data['accessToken'] as String, + refresh: data['refreshToken'] as String, + ); + return true; + } + return false; + } catch (_) { + return false; + } finally { + _refreshing = false; + } + } + + Future> _retry(RequestOptions options) { + return _dio.fetch(options); + } + + Future get(String path, {Map? query}) { + return _request(() => _dio.get(path, queryParameters: query)); + } + + Future post(String path, {Object? data}) { + return _request(() => _dio.post(path, data: data)); + } + + Future put(String path, {Object? data}) { + return _request(() => _dio.put(path, data: data)); + } + + Future delete(String path, {Object? data}) { + return _request(() => _dio.delete(path, data: data)); + } + + Future _request(Future> Function() send) async { + try { + final response = await send(); + final body = response.data; + if (body is Map && body.containsKey('code')) { + final code = body['code']; + if (code == 0) return body['data']; + throw ApiException(code as int, (body['message'] as String?) ?? ''); + } + return body; + } on DioException catch (e) { + throw ApiException( + e.response?.statusCode ?? -1, + e.message ?? 'network error', + ); + } + } +} diff --git a/lib/core/data/services/auth_controller.dart b/lib/core/data/services/auth_controller.dart new file mode 100644 index 0000000..2c6f519 --- /dev/null +++ b/lib/core/data/services/auth_controller.dart @@ -0,0 +1,65 @@ +// Author: fruit.com.cn + +import 'package:flutter/foundation.dart'; + +import '../repositories/auth_repository.dart'; + +enum AuthStatus { unknown, authenticated, unauthenticated } + +/// 登录状态与账号操作。 +class AuthController extends ChangeNotifier { + AuthController(this._repository); + + final AuthRepository _repository; + + AuthStatus _status = AuthStatus.unknown; + AuthStatus get status => _status; + + Future sendOtp({ + required String scene, + required String targetType, + required String target, + }) { + return _repository.sendOtp( + scene: scene, + targetType: targetType, + target: target, + ); + } + + Future loginWithCode({ + required String targetType, + required String target, + required String code, + }) async { + final session = await _repository.loginWithCode( + targetType: targetType, + target: target, + code: code, + ); + await _repository.saveSession(session); + _status = AuthStatus.authenticated; + notifyListeners(); + } + + Future loginWithPassword({ + required String targetType, + required String target, + required String password, + }) async { + final session = await _repository.loginWithPassword( + targetType: targetType, + target: target, + password: password, + ); + await _repository.saveSession(session); + _status = AuthStatus.authenticated; + notifyListeners(); + } + + Future logout() async { + await _repository.logout(); + _status = AuthStatus.unauthenticated; + notifyListeners(); + } +} diff --git a/lib/core/data/services/token_storage.dart b/lib/core/data/services/token_storage.dart new file mode 100644 index 0000000..beab898 --- /dev/null +++ b/lib/core/data/services/token_storage.dart @@ -0,0 +1,34 @@ +// Author: fruit.com.cn + +import 'package:shared_preferences/shared_preferences.dart'; + +/// 令牌本地存储。第一阶段用 shared_preferences,后续可换 secure storage。 +class TokenStorage { + static const _accessKey = 'auth_access_token'; + static const _refreshKey = 'auth_refresh_token'; + + Future saveTokens({ + required String access, + required String refresh, + }) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setString(_accessKey, access); + await prefs.setString(_refreshKey, refresh); + } + + Future readAccessToken() async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getString(_accessKey); + } + + Future readRefreshToken() async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getString(_refreshKey); + } + + Future clear() async { + final prefs = await SharedPreferences.getInstance(); + await prefs.remove(_accessKey); + await prefs.remove(_refreshKey); + } +} diff --git a/lib/pages/login/login_view.dart b/lib/pages/login/login_view.dart index f210714..f6f28c6 100644 --- a/lib/pages/login/login_view.dart +++ b/lib/pages/login/login_view.dart @@ -1,11 +1,12 @@ // Author: fruit.com.cn import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import '../../core/data/services/auth_controller.dart'; import '../home/home_view.dart'; /// 登录页:Google / 手机号 / 邮箱 三种登录入口 + 游客浏览。 -/// 当前为 UI 占位实现,真实登录逻辑在接入后端 /app/auth/** 后补充。 class LoginPage extends StatefulWidget { const LoginPage({super.key}); @@ -23,6 +24,132 @@ class _LoginPageState extends State { ); } + Future _showLoginSheet(String targetType) async { + final auth = context.read(); + final targetCtrl = TextEditingController(); + final codeCtrl = TextEditingController(); + final isPhone = targetType == 'PHONE'; + + await showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: const Color(0xFF161822), + builder: (sheetContext) => Padding( + padding: EdgeInsets.only( + bottom: MediaQuery.of(sheetContext).viewInsets.bottom, + ), + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + isPhone ? 'Phone Login' : 'Email Login', + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w700, + color: Colors.white, + ), + ), + const SizedBox(height: 16), + TextField( + controller: targetCtrl, + keyboardType: isPhone + ? TextInputType.phone + : TextInputType.emailAddress, + style: const TextStyle(color: Colors.white), + decoration: InputDecoration( + hintText: isPhone ? 'Phone number' : 'Email', + hintStyle: const TextStyle(color: Colors.white38), + filled: true, + fillColor: Colors.white.withValues(alpha: 0.05), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + ), + ), + const SizedBox(height: 12), + Row( + children: [ + Expanded( + child: TextField( + controller: codeCtrl, + keyboardType: TextInputType.number, + style: const TextStyle(color: Colors.white), + decoration: InputDecoration( + hintText: 'Code', + hintStyle: const TextStyle(color: Colors.white38), + filled: true, + fillColor: Colors.white.withValues(alpha: 0.05), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + ), + ), + ), + const SizedBox(width: 12), + TextButton( + onPressed: () async { + try { + await auth.sendOtp( + scene: 'LOGIN', + targetType: targetType, + target: targetCtrl.text.trim(), + ); + if (sheetContext.mounted) { + ScaffoldMessenger.of(sheetContext).showSnackBar( + const SnackBar(content: Text('Code sent')), + ); + } + } catch (e) { + if (sheetContext.mounted) { + ScaffoldMessenger.of(sheetContext).showSnackBar( + SnackBar(content: Text('$e')), + ); + } + } + }, + child: const Text('Send Code'), + ), + ], + ), + const SizedBox(height: 20), + FilledButton( + onPressed: () async { + try { + await auth.loginWithCode( + targetType: targetType, + target: targetCtrl.text.trim(), + code: codeCtrl.text.trim(), + ); + if (sheetContext.mounted) { + Navigator.of(sheetContext).pop(); + } + _enterHome(); + } catch (e) { + if (sheetContext.mounted) { + ScaffoldMessenger.of(sheetContext).showSnackBar( + SnackBar(content: Text('$e')), + ); + } + } + }, + style: FilledButton.styleFrom( + backgroundColor: _orange, + padding: const EdgeInsets.symmetric(vertical: 14), + ), + child: const Text('Sign In'), + ), + ], + ), + ), + ), + ); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -86,14 +213,14 @@ class _LoginPageState extends State { icon: Icons.phone_iphone, iconColor: _orange, label: 'Continue with Phone Number', - onTap: _enterHome, + onTap: () => _showLoginSheet('PHONE'), ), const SizedBox(height: 14), _DarkButton( icon: Icons.email_outlined, iconColor: _purple, label: 'Continue with Email', - onTap: _enterHome, + onTap: () => _showLoginSheet('EMAIL'), ), const SizedBox(height: 32), const _BonusPill(), diff --git a/pubspec.lock b/pubspec.lock index ee2e934..0d11dbe 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -6,7 +6,7 @@ packages: description: name: archive sha256: "0d58d1fa1fddf6588251ccf2819f3fb3b6d0452c1b2813ac7413a05592bfbefe" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.5.2" args: @@ -14,7 +14,7 @@ packages: description: name: args sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.7.0" async: @@ -22,7 +22,7 @@ packages: description: name: async sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.13.1" boolean_selector: @@ -30,7 +30,7 @@ packages: description: name: boolean_selector sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.2" characters: @@ -38,7 +38,7 @@ packages: description: name: characters sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.4.1" checked_yaml: @@ -46,7 +46,7 @@ packages: description: name: checked_yaml sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.4" cli_util: @@ -54,23 +54,23 @@ packages: description: name: cli_util sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.4.2" clock: dependency: transitive description: name: clock - sha256: e51d50bca3217c9a9fa2b41a30e4a38971133f5f9ec7a3d57bae095007f1d28e - url: "https://pub.flutter-io.cn" + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" source: hosted - version: "1.1.3" + version: "1.1.2" collection: dependency: transitive description: name: collection sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.19.1" csslib: @@ -78,7 +78,7 @@ packages: description: name: csslib sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.2" cupertino_icons: @@ -86,15 +86,31 @@ packages: description: name: cupertino_icons sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.9" + dio: + dependency: "direct main" + description: + name: dio + sha256: "852ec3b48cc431ac04fff978413c541502b67ffc3e26921e74e3d994694192c1" + url: "https://pub.dev" + source: hosted + version: "5.11.1" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "3a1b2cd7be71086f38504956e3ebcd2837288d231ff454bafa78021244102bfc" + url: "https://pub.dev" + source: hosted + version: "2.2.2" fake_async: dependency: transitive description: name: fake_async sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.3.3" ffi: @@ -102,7 +118,7 @@ packages: description: name: ffi sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.2.0" file: @@ -110,7 +126,7 @@ packages: description: name: file sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "7.0.1" flutter: @@ -123,7 +139,7 @@ packages: description: name: flutter_launcher_icons sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.14.4" flutter_lints: @@ -131,7 +147,7 @@ packages: description: name: flutter_lints sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.0.0" flutter_localizations: @@ -154,31 +170,39 @@ packages: description: name: html sha256: "43b67b8f43321ab066817dfac5619596c98bb1b61624e77203bb4351785f9699" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.15.7" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" image: dependency: transitive description: name: image sha256: "89ed75b2f8231fe676808c5564f9590d728024ded7ac782a7ed8474f1c95b72a" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.12.0" intl: dependency: transitive description: name: intl - sha256: "1ca20c894b1717686a2319b8548763d812bc0aabdac580420a44c5178c57a867" - url: "https://pub.flutter-io.cn" + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" source: hosted - version: "0.20.3" + version: "0.20.2" json_annotation: dependency: transitive description: name: json_annotation sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.12.0" leak_tracker: @@ -186,7 +210,7 @@ packages: description: name: leak_tracker sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "11.0.2" leak_tracker_flutter_testing: @@ -194,7 +218,7 @@ packages: description: name: leak_tracker_flutter_testing sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.10" leak_tracker_testing: @@ -202,7 +226,7 @@ packages: description: name: leak_tracker_testing sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.2" lints: @@ -210,39 +234,47 @@ packages: description: name: lints sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.1.0" matcher: dependency: transitive description: name: matcher - sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd" - url: "https://pub.flutter-io.cn" + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + url: "https://pub.dev" source: hosted - version: "0.12.20" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.13.0" meta: dependency: transitive description: name: meta - sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9" - url: "https://pub.flutter-io.cn" + sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" + url: "https://pub.dev" source: hosted - version: "1.19.0" + version: "1.18.0" + mime: + dependency: transitive + description: + name: mime + sha256: bd47de35f07e27267e69c8c8b22edf9473bfee170a60d60fcc93730c5144b7f6 + url: "https://pub.dev" + source: hosted + version: "2.1.0" nested: dependency: transitive description: name: nested sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.0" path: @@ -250,7 +282,7 @@ packages: description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.9.1" path_provider_linux: @@ -258,7 +290,7 @@ packages: description: name: path_provider_linux sha256: "58c2005f147315b11e9b4a7bc889cd5203e250cba8e3f012dae259b4972b5c16" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.2.2" path_provider_platform_interface: @@ -266,7 +298,7 @@ packages: description: name: path_provider_platform_interface sha256: "484838772624c3a4b94f1e44a3e19897fee738f2d5c4ce448443b0417f7c9dda" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.3" path_provider_windows: @@ -274,7 +306,7 @@ packages: description: name: path_provider_windows sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.3.0" platform: @@ -282,7 +314,7 @@ packages: description: name: platform sha256: a36d119c13416516a7b5913fbe8af8531e11633d784c550b2125f76c758524ec - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.2.0" plugin_platform_interface: @@ -290,7 +322,7 @@ packages: description: name: plugin_platform_interface sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.8" posix: @@ -298,7 +330,7 @@ packages: description: name: posix sha256: bc1bad54ad2b735816e31f8d4600cfde6c7839975085ddfbca48b6c9f7c4044e - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.5.2" provider: @@ -306,7 +338,7 @@ packages: description: name: provider sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.1.5+1" shared_preferences: @@ -314,7 +346,7 @@ packages: description: name: shared_preferences sha256: "1e85e0dccf5b4a016077fea7dfbb4a7cb39c759ac1596fcf60c06deadb7da91f" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.5.6" shared_preferences_android: @@ -322,7 +354,7 @@ packages: description: name: shared_preferences_android sha256: "1e12aafe408aa50da80edfd679a2a6bf63ba7ab37c7fa98286da459a757b3399" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.28" shared_preferences_foundation: @@ -330,7 +362,7 @@ packages: description: name: shared_preferences_foundation sha256: "2ec3934efa51e46117f23031cc141b8fc878e8525b94ec1ea4f7f586cf1b47ea" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.5.7" shared_preferences_linux: @@ -338,7 +370,7 @@ packages: description: name: shared_preferences_linux sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.1" shared_preferences_platform_interface: @@ -346,7 +378,7 @@ packages: description: name: shared_preferences_platform_interface sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.2" shared_preferences_web: @@ -354,7 +386,7 @@ packages: description: name: shared_preferences_web sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.3" shared_preferences_windows: @@ -362,7 +394,7 @@ packages: description: name: shared_preferences_windows sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.1" sky_engine: @@ -375,23 +407,23 @@ packages: description: name: source_span sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.10.2" stack_trace: dependency: transitive description: name: stack_trace - sha256: "277654b3034d17ac6f9f1cb5595db011b1d5d41e8806866db28e0abaa101c490" - url: "https://pub.flutter-io.cn" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" source: hosted - version: "1.12.2" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.4" string_scanner: @@ -399,7 +431,7 @@ packages: description: name: string_scanner sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.4.1" term_glyph: @@ -407,31 +439,39 @@ packages: description: name: term_glyph sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.2.2" test_api: dependency: transitive description: name: test_api - sha256: "2a122cbe059f8b610d3a5415f42e255b6c17b1f21eee1d960f31080237fb4f11" - url: "https://pub.flutter-io.cn" + sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" + url: "https://pub.dev" source: hosted - version: "0.7.12" + version: "0.7.11" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" vector_math: dependency: transitive description: name: vector_math - sha256: "92b9910f66ed1057fd4da7b040ae7c74cafacf885bdc81be496928d5049b032d" - url: "https://pub.flutter-io.cn" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" source: hosted - version: "2.4.3" + version: "2.2.0" video_player: dependency: "direct main" description: name: video_player sha256: "07ae1e03f2d4c8e2e9a75ee678f90acbc4506d4458a9c628512d119a1eee5754" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.14.1" video_player_android: @@ -439,7 +479,7 @@ packages: description: name: video_player_android sha256: d27054dea34d748a44f06d433a57005d2aac69944485b0abbaed3303dbcfa3f1 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.12.2" video_player_avfoundation: @@ -447,7 +487,7 @@ packages: description: name: video_player_avfoundation sha256: "4f8a845173377ab250a50f9b9dfe4a43500cc76a7c49dd8410d02d8fc29dfd8b" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.12.1" video_player_platform_interface: @@ -455,7 +495,7 @@ packages: description: name: video_player_platform_interface sha256: "92c0fbabe20c788e71fd10d26cea998d0d253282e65d145aed0818731cf593ce" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.9.0" video_player_web: @@ -463,23 +503,23 @@ packages: description: name: video_player_web sha256: "9f3c00be2ef9b76a95d94ac5119fb843dca6f2c69e6c9968f6f2b6c9e7afbdeb" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.0" vm_service: dependency: transitive description: name: vm_service - sha256: "5f37239c4851efcef929cea7824e76df7f2f0970aef85d66bbc430afa40e72f0" - url: "https://pub.flutter-io.cn" + sha256: "89b11d62e1040ee681af3b4f1c97a803ecbf010c6e8b9c5896ee5b08c3bf7b78" + url: "https://pub.dev" source: hosted - version: "15.3.0" + version: "15.4.0" web: dependency: transitive description: name: web sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.1" xdg_directories: @@ -487,7 +527,7 @@ packages: description: name: xdg_directories sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.0" yaml: @@ -495,7 +535,7 @@ packages: description: name: yaml sha256: f67cdd8e07d3c6329146aaef1ba043542b3134c12489f553ca9a7435d1068aea - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.1.4" sdks: diff --git a/pubspec.yaml b/pubspec.yaml index fb5bbc0..6343d52 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,6 +39,7 @@ dependencies: provider: ^6.1.5+1 video_player: ^2.14.0 shared_preferences: ^2.5.5 + dio: ^5.7.0 flutter_localizations: sdk: flutter