feat: A1 网络层与鉴权 + A2 账号登录对接
- 引入 dio,封装统一 ApiClient(BaseUrl/Token 注入/401 自动刷新/{code,message,data} 解包)
- TokenStorage 令牌持久化,AuthController 登录状态管理
- AuthRepository 封装 otp/login/logout 接口
- 登录页对接手机号/邮箱验证码登录(Google 登录待 OAuth client 配置)
This commit is contained in:
1 parent
015677d1a8
commit
a7ac3b7fc7
9 files changed
+602
-78
No files matched your search
@@ -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<PlayerFactory>(create: (_) => playerFactory),
|
||||
Provider(create: (_) => ApiClient()),
|
||||
Provider(create: (_) => TokenStorage()),
|
||||
Provider(
|
||||
create: (ctx) => AuthRepository(
|
||||
ctx.read<ApiClient>(),
|
||||
ctx.read<TokenStorage>(),
|
||||
),
|
||||
),
|
||||
ChangeNotifierProvider(
|
||||
create: (ctx) => AuthController(ctx.read<AuthRepository>()),
|
||||
),
|
||||
ChangeNotifierProvider(
|
||||
create: (_) => CatalogController(DemoCatalogRepository())..load(),
|
||||
),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<String, dynamic> 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<void> 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<AuthSession> 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<String, dynamic>);
|
||||
}
|
||||
|
||||
/// 密码登录。
|
||||
Future<AuthSession> 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<String, dynamic>);
|
||||
}
|
||||
|
||||
/// 保存会话令牌。
|
||||
Future<void> saveSession(AuthSession session) async {
|
||||
await _tokenStorage.saveTokens(
|
||||
access: session.accessToken,
|
||||
refresh: session.refreshToken,
|
||||
);
|
||||
}
|
||||
|
||||
/// 登出并清理本地令牌。
|
||||
Future<void> logout() async {
|
||||
await _client.post('/app/auth/logout');
|
||||
await _tokenStorage.clear();
|
||||
}
|
||||
}
|
||||
@@ -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<void> _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<void> _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<bool> _refresh() async {
|
||||
_refreshing = true;
|
||||
try {
|
||||
final refresh = await _tokenStorage.readRefreshToken();
|
||||
if (refresh == null || refresh.isEmpty) return false;
|
||||
final response = await _dio.post<dynamic>(
|
||||
'/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<Response<dynamic>> _retry(RequestOptions options) {
|
||||
return _dio.fetch<dynamic>(options);
|
||||
}
|
||||
|
||||
Future<dynamic> get(String path, {Map<String, dynamic>? query}) {
|
||||
return _request(() => _dio.get<dynamic>(path, queryParameters: query));
|
||||
}
|
||||
|
||||
Future<dynamic> post(String path, {Object? data}) {
|
||||
return _request(() => _dio.post<dynamic>(path, data: data));
|
||||
}
|
||||
|
||||
Future<dynamic> put(String path, {Object? data}) {
|
||||
return _request(() => _dio.put<dynamic>(path, data: data));
|
||||
}
|
||||
|
||||
Future<dynamic> delete(String path, {Object? data}) {
|
||||
return _request(() => _dio.delete<dynamic>(path, data: data));
|
||||
}
|
||||
|
||||
Future<dynamic> _request(Future<Response<dynamic>> 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',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<void> sendOtp({
|
||||
required String scene,
|
||||
required String targetType,
|
||||
required String target,
|
||||
}) {
|
||||
return _repository.sendOtp(
|
||||
scene: scene,
|
||||
targetType: targetType,
|
||||
target: target,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> 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<void> 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<void> logout() async {
|
||||
await _repository.logout();
|
||||
_status = AuthStatus.unauthenticated;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
@@ -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<void> saveTokens({
|
||||
required String access,
|
||||
required String refresh,
|
||||
}) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(_accessKey, access);
|
||||
await prefs.setString(_refreshKey, refresh);
|
||||
}
|
||||
|
||||
Future<String?> readAccessToken() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
return prefs.getString(_accessKey);
|
||||
}
|
||||
|
||||
Future<String?> readRefreshToken() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
return prefs.getString(_refreshKey);
|
||||
}
|
||||
|
||||
Future<void> clear() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.remove(_accessKey);
|
||||
await prefs.remove(_refreshKey);
|
||||
}
|
||||
}
|
||||
@@ -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<LoginPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showLoginSheet(String targetType) async {
|
||||
final auth = context.read<AuthController>();
|
||||
final targetCtrl = TextEditingController();
|
||||
final codeCtrl = TextEditingController();
|
||||
final isPhone = targetType == 'PHONE';
|
||||
|
||||
await showModalBottomSheet<void>(
|
||||
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<LoginPage> {
|
||||
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(),
|
||||
|
||||
+115
-75
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in new issue
Block a user