Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45e867561d | ||
|
|
64fa3b1efe | ||
|
|
faaf2f7283 | ||
|
|
070ec78668 | ||
|
|
88a401512d | ||
|
|
a7ac3b7fc7 | ||
|
|
015677d1a8 |
No files matched your search
@@ -32,6 +32,9 @@ android {
|
|||||||
// TODO: Add your own signing config for the release build.
|
// TODO: Add your own signing config for the release build.
|
||||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||||
signingConfig = signingConfigs.getByName("debug")
|
signingConfig = signingConfigs.getByName("debug")
|
||||||
|
// 反编译保护(法律红线配套):开启 R8 混淆 + 资源压缩,发布前替换正式签名。
|
||||||
|
isMinifyEnabled = true
|
||||||
|
isShrinkResources = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||||
#include "Generated.xcconfig"
|
#include "Generated.xcconfig"
|
||||||
@@ -1 +1,2 @@
|
|||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||||
#include "Generated.xcconfig"
|
#include "Generated.xcconfig"
|
||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
# Uncomment this line to define a global platform for your project
|
||||||
|
# platform :ios, '13.0'
|
||||||
|
|
||||||
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
|
|
||||||
|
project 'Runner', {
|
||||||
|
'Debug' => :debug,
|
||||||
|
'Profile' => :release,
|
||||||
|
'Release' => :release,
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutter_root
|
||||||
|
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
|
||||||
|
unless File.exist?(generated_xcode_build_settings_path)
|
||||||
|
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
|
||||||
|
end
|
||||||
|
|
||||||
|
File.foreach(generated_xcode_build_settings_path) do |line|
|
||||||
|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
||||||
|
return matches[1].strip if matches
|
||||||
|
end
|
||||||
|
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
|
||||||
|
end
|
||||||
|
|
||||||
|
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||||
|
|
||||||
|
flutter_ios_podfile_setup
|
||||||
|
|
||||||
|
target 'Runner' do
|
||||||
|
use_frameworks!
|
||||||
|
|
||||||
|
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
||||||
|
target 'RunnerTests' do
|
||||||
|
inherit! :search_paths
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
post_install do |installer|
|
||||||
|
installer.pods_project.targets.each do |target|
|
||||||
|
flutter_additional_ios_build_settings(target)
|
||||||
|
end
|
||||||
|
end
|
||||||
+32
-7
@@ -4,14 +4,21 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'core/config/locale_controller.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/catalog_repository.dart';
|
||||||
import 'core/data/repositories/engagement_repository.dart';
|
import 'core/data/repositories/engagement_repository.dart';
|
||||||
|
import 'core/data/repositories/http_catalog_repository.dart';
|
||||||
|
import 'core/data/services/api_client.dart';
|
||||||
|
import 'core/data/services/app_catalog_controller.dart';
|
||||||
|
import 'core/data/services/auth_controller.dart';
|
||||||
import 'core/data/services/catalog_controller.dart';
|
import 'core/data/services/catalog_controller.dart';
|
||||||
import 'core/data/services/engagement_controller.dart';
|
import 'core/data/services/engagement_controller.dart';
|
||||||
|
import 'core/data/services/event_tracker.dart';
|
||||||
import 'core/data/services/playback_session.dart';
|
import 'core/data/services/playback_session.dart';
|
||||||
|
import 'core/data/services/token_storage.dart';
|
||||||
import 'core/data/services/video_player_port.dart';
|
import 'core/data/services/video_player_port.dart';
|
||||||
import 'l10n/app_localizations.dart';
|
import 'l10n/app_localizations.dart';
|
||||||
import 'pages/home/home_view.dart';
|
import 'pages/login/login_view.dart';
|
||||||
|
|
||||||
class LumaDramaApp extends StatelessWidget {
|
class LumaDramaApp extends StatelessWidget {
|
||||||
const LumaDramaApp({super.key, this.playerFactory = VideoPlayerPort.new});
|
const LumaDramaApp({super.key, this.playerFactory = VideoPlayerPort.new});
|
||||||
@@ -24,6 +31,24 @@ class LumaDramaApp extends StatelessWidget {
|
|||||||
providers: [
|
providers: [
|
||||||
Provider(create: (_) => PlaybackCoordinator()),
|
Provider(create: (_) => PlaybackCoordinator()),
|
||||||
Provider<PlayerFactory>(create: (_) => playerFactory),
|
Provider<PlayerFactory>(create: (_) => playerFactory),
|
||||||
|
Provider(create: (_) => ApiClient()),
|
||||||
|
Provider(create: (ctx) => EventTracker(ctx.read<ApiClient>())),
|
||||||
|
Provider(create: (_) => TokenStorage()),
|
||||||
|
Provider(
|
||||||
|
create: (ctx) => AuthRepository(
|
||||||
|
ctx.read<ApiClient>(),
|
||||||
|
ctx.read<TokenStorage>(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ChangeNotifierProvider(
|
||||||
|
create: (ctx) => AuthController(ctx.read<AuthRepository>()),
|
||||||
|
),
|
||||||
|
Provider(
|
||||||
|
create: (ctx) => HttpCatalogRepository(ctx.read<ApiClient>()),
|
||||||
|
),
|
||||||
|
ChangeNotifierProvider(
|
||||||
|
create: (ctx) => AppCatalogController(ctx.read<HttpCatalogRepository>()),
|
||||||
|
),
|
||||||
ChangeNotifierProvider(
|
ChangeNotifierProvider(
|
||||||
create: (_) => CatalogController(DemoCatalogRepository())..load(),
|
create: (_) => CatalogController(DemoCatalogRepository())..load(),
|
||||||
),
|
),
|
||||||
@@ -49,23 +74,23 @@ class LumaDramaApp extends StatelessWidget {
|
|||||||
return supported.first;
|
return supported.first;
|
||||||
},
|
},
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
themeMode: ThemeMode.system,
|
themeMode: ThemeMode.dark,
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
colorScheme: ColorScheme.fromSeed(
|
colorScheme: ColorScheme.fromSeed(
|
||||||
seedColor: const Color(0xFF6D4AFF),
|
seedColor: const Color(0xFFFF5B3D),
|
||||||
),
|
),
|
||||||
scaffoldBackgroundColor: const Color(0xFFF8F7FC),
|
scaffoldBackgroundColor: const Color(0xFF0B0C10),
|
||||||
),
|
),
|
||||||
darkTheme: ThemeData(
|
darkTheme: ThemeData(
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
colorScheme: ColorScheme.fromSeed(
|
colorScheme: ColorScheme.fromSeed(
|
||||||
seedColor: const Color(0xFF9B86FF),
|
seedColor: const Color(0xFFFF5B3D),
|
||||||
brightness: Brightness.dark,
|
brightness: Brightness.dark,
|
||||||
),
|
),
|
||||||
scaffoldBackgroundColor: const Color(0xFF101019),
|
scaffoldBackgroundColor: const Color(0xFF0B0C10),
|
||||||
),
|
),
|
||||||
home: const HomePage(),
|
home: const LoginPage(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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,188 @@
|
|||||||
|
// Author: fruit.com.cn
|
||||||
|
|
||||||
|
// 与后端 /app/** 内容接口对齐的数据模型。
|
||||||
|
|
||||||
|
class AppEpisode {
|
||||||
|
AppEpisode({
|
||||||
|
required this.id,
|
||||||
|
required this.number,
|
||||||
|
required this.title,
|
||||||
|
required this.durationSec,
|
||||||
|
required this.locked,
|
||||||
|
required this.unlockCoin,
|
||||||
|
this.playUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
final int id;
|
||||||
|
final int number;
|
||||||
|
final String title;
|
||||||
|
final int durationSec;
|
||||||
|
final bool locked;
|
||||||
|
final int unlockCoin;
|
||||||
|
final String? playUrl;
|
||||||
|
|
||||||
|
factory AppEpisode.fromJson(Map<String, dynamic> json) => AppEpisode(
|
||||||
|
id: json['id'] as int,
|
||||||
|
number: json['number'] as int,
|
||||||
|
title: json['title'] as String? ?? '',
|
||||||
|
durationSec: json['durationSec'] as int? ?? 0,
|
||||||
|
locked: json['locked'] as bool? ?? false,
|
||||||
|
unlockCoin: json['unlockCoin'] as int? ?? 0,
|
||||||
|
playUrl: json['playUrl'] as String?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppDramaCard {
|
||||||
|
AppDramaCard({
|
||||||
|
required this.id,
|
||||||
|
required this.title,
|
||||||
|
required this.cover,
|
||||||
|
required this.synopsis,
|
||||||
|
required this.genre,
|
||||||
|
required this.category,
|
||||||
|
required this.totalEpisodes,
|
||||||
|
required this.isHot,
|
||||||
|
required this.viewCount,
|
||||||
|
this.preview,
|
||||||
|
});
|
||||||
|
|
||||||
|
final int id;
|
||||||
|
final String title;
|
||||||
|
final String cover;
|
||||||
|
final String synopsis;
|
||||||
|
final String genre;
|
||||||
|
final String category;
|
||||||
|
final int totalEpisodes;
|
||||||
|
final bool isHot;
|
||||||
|
final int viewCount;
|
||||||
|
final AppEpisode? preview;
|
||||||
|
|
||||||
|
factory AppDramaCard.fromJson(Map<String, dynamic> json) => AppDramaCard(
|
||||||
|
id: json['id'] as int,
|
||||||
|
title: json['title'] as String? ?? '',
|
||||||
|
cover: json['cover'] as String? ?? '',
|
||||||
|
synopsis: json['synopsis'] as String? ?? '',
|
||||||
|
genre: json['genre'] as String? ?? '',
|
||||||
|
category: json['category'] as String? ?? '',
|
||||||
|
totalEpisodes: json['totalEpisodes'] as int? ?? 0,
|
||||||
|
isHot: json['isHot'] as bool? ?? false,
|
||||||
|
viewCount: json['viewCount'] as int? ?? 0,
|
||||||
|
preview: json['preview'] == null
|
||||||
|
? null
|
||||||
|
: AppEpisode.fromJson(json['preview'] as Map<String, dynamic>),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppDramaDetail {
|
||||||
|
AppDramaDetail({
|
||||||
|
required this.id,
|
||||||
|
required this.title,
|
||||||
|
required this.synopsis,
|
||||||
|
required this.cover,
|
||||||
|
required this.genre,
|
||||||
|
required this.category,
|
||||||
|
required this.totalEpisodes,
|
||||||
|
required this.freeEpisodes,
|
||||||
|
required this.unlockCoin,
|
||||||
|
required this.isHot,
|
||||||
|
required this.viewCount,
|
||||||
|
required this.episodes,
|
||||||
|
});
|
||||||
|
|
||||||
|
final int id;
|
||||||
|
final String title;
|
||||||
|
final String synopsis;
|
||||||
|
final String cover;
|
||||||
|
final String genre;
|
||||||
|
final String category;
|
||||||
|
final int totalEpisodes;
|
||||||
|
final int freeEpisodes;
|
||||||
|
final int unlockCoin;
|
||||||
|
final bool isHot;
|
||||||
|
final int viewCount;
|
||||||
|
final List<AppEpisode> episodes;
|
||||||
|
|
||||||
|
factory AppDramaDetail.fromJson(Map<String, dynamic> json) => AppDramaDetail(
|
||||||
|
id: json['id'] as int,
|
||||||
|
title: json['title'] as String? ?? '',
|
||||||
|
synopsis: json['synopsis'] as String? ?? '',
|
||||||
|
cover: json['cover'] as String? ?? '',
|
||||||
|
genre: json['genre'] as String? ?? '',
|
||||||
|
category: json['category'] as String? ?? '',
|
||||||
|
totalEpisodes: json['totalEpisodes'] as int? ?? 0,
|
||||||
|
freeEpisodes: json['freeEpisodes'] as int? ?? 0,
|
||||||
|
unlockCoin: json['unlockCoin'] as int? ?? 0,
|
||||||
|
isHot: json['isHot'] as bool? ?? false,
|
||||||
|
viewCount: json['viewCount'] as int? ?? 0,
|
||||||
|
episodes: (json['episodes'] as List? ?? const [])
|
||||||
|
.map((e) => AppEpisode.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppBanner {
|
||||||
|
AppBanner({
|
||||||
|
required this.id,
|
||||||
|
required this.imageUrl,
|
||||||
|
required this.linkType,
|
||||||
|
required this.linkValue,
|
||||||
|
});
|
||||||
|
|
||||||
|
final int id;
|
||||||
|
final String imageUrl;
|
||||||
|
final String linkType;
|
||||||
|
final String linkValue;
|
||||||
|
|
||||||
|
factory AppBanner.fromJson(Map<String, dynamic> json) => AppBanner(
|
||||||
|
id: json['id'] as int,
|
||||||
|
imageUrl: json['imageUrl'] as String? ?? '',
|
||||||
|
linkType: json['linkType'] as String? ?? '',
|
||||||
|
linkValue: json['linkValue'] as String? ?? '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppCategory {
|
||||||
|
AppCategory({required this.id, required this.name});
|
||||||
|
|
||||||
|
final int id;
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
factory AppCategory.fromJson(Map<String, dynamic> json) =>
|
||||||
|
AppCategory(id: json['id'] as int, name: json['name'] as String? ?? '');
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppSection {
|
||||||
|
AppSection({
|
||||||
|
required this.code,
|
||||||
|
required this.title,
|
||||||
|
required this.dramas,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String code;
|
||||||
|
final String title;
|
||||||
|
final List<AppDramaCard> dramas;
|
||||||
|
|
||||||
|
factory AppSection.fromJson(Map<String, dynamic> json) => AppSection(
|
||||||
|
code: json['code'] as String? ?? '',
|
||||||
|
title: json['title'] as String? ?? '',
|
||||||
|
dramas: (json['dramas'] as List? ?? const [])
|
||||||
|
.map((e) => AppDramaCard.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppFeed {
|
||||||
|
AppFeed({required this.banners, required this.sections});
|
||||||
|
|
||||||
|
final List<AppBanner> banners;
|
||||||
|
final List<AppSection> sections;
|
||||||
|
|
||||||
|
factory AppFeed.fromJson(Map<String, dynamic> json) => AppFeed(
|
||||||
|
banners: (json['banners'] as List? ?? const [])
|
||||||
|
.map((e) => AppBanner.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
sections: (json['sections'] as List? ?? const [])
|
||||||
|
.map((e) => AppSection.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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,33 @@
|
|||||||
|
// Author: fruit.com.cn
|
||||||
|
|
||||||
|
import '../models/app_catalog.dart';
|
||||||
|
import '../services/api_client.dart';
|
||||||
|
|
||||||
|
/// 对接后端 `/app/**` 内容接口(A3)。
|
||||||
|
class HttpCatalogRepository {
|
||||||
|
HttpCatalogRepository(this._client);
|
||||||
|
|
||||||
|
final ApiClient _client;
|
||||||
|
|
||||||
|
Future<AppFeed> fetchHomeFeed() async {
|
||||||
|
final data = await _client.get('/app/home/feed');
|
||||||
|
return AppFeed.fromJson(data as Map<String, dynamic>);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<AppFeed> fetchDiscoverFeed() async {
|
||||||
|
final data = await _client.get('/app/discover/feed');
|
||||||
|
return AppFeed.fromJson(data as Map<String, dynamic>);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<AppCategory>> fetchCategories() async {
|
||||||
|
final data = await _client.get('/app/categories');
|
||||||
|
return (data as List)
|
||||||
|
.map((e) => AppCategory.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<AppDramaDetail> fetchDramaDetail(int id) async {
|
||||||
|
final data = await _client.get('/app/dramas/$id');
|
||||||
|
return AppDramaDetail.fromJson(data as Map<String, dynamic>);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
// Author: fruit.com.cn
|
||||||
|
|
||||||
|
import '../services/api_client.dart';
|
||||||
|
|
||||||
|
/// 评论、点赞、举报(A6)。
|
||||||
|
class InteractionRepository {
|
||||||
|
InteractionRepository(this._client);
|
||||||
|
|
||||||
|
final ApiClient _client;
|
||||||
|
|
||||||
|
Future<void> postComment({
|
||||||
|
required int dramaId,
|
||||||
|
required String content,
|
||||||
|
int? rootId,
|
||||||
|
}) async {
|
||||||
|
await _client.post('/app/comments', data: {
|
||||||
|
'dramaId': dramaId,
|
||||||
|
'content': content,
|
||||||
|
if (rootId != null) 'rootId': rootId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<dynamic>> fetchComments({
|
||||||
|
required int dramaId,
|
||||||
|
int? cursor,
|
||||||
|
int size = 20,
|
||||||
|
}) async {
|
||||||
|
final data = await _client.get('/app/comments', query: {
|
||||||
|
'dramaId': dramaId,
|
||||||
|
if (cursor != null) 'cursor': cursor,
|
||||||
|
'size': size,
|
||||||
|
});
|
||||||
|
return data as List;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteComment(int id) async {
|
||||||
|
await _client.delete('/app/comments/$id');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> toggleLike({
|
||||||
|
required String targetType,
|
||||||
|
required int targetId,
|
||||||
|
}) async {
|
||||||
|
await _client.post('/app/likes/toggle', data: {
|
||||||
|
'targetType': targetType,
|
||||||
|
'targetId': targetId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> report({
|
||||||
|
required String targetType,
|
||||||
|
required int targetId,
|
||||||
|
required String reason,
|
||||||
|
}) async {
|
||||||
|
await _client.post('/app/reports', data: {
|
||||||
|
'targetType': targetType,
|
||||||
|
'targetId': targetId,
|
||||||
|
'reason': reason,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
// Author: fruit.com.cn
|
||||||
|
|
||||||
|
import '../services/api_client.dart';
|
||||||
|
|
||||||
|
/// 播放票据(A4)。
|
||||||
|
class PlaybackRepository {
|
||||||
|
PlaybackRepository(this._client);
|
||||||
|
|
||||||
|
final ApiClient _client;
|
||||||
|
|
||||||
|
/// 签发播放票据与签名地址,客户端原样使用 playUrl。
|
||||||
|
Future<Map<String, dynamic>> fetchPlayTicket(int episodeId) async {
|
||||||
|
final data = await _client.get('/app/episodes/$episodeId/play');
|
||||||
|
return data as Map<String, dynamic>;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// Author: fruit.com.cn
|
||||||
|
|
||||||
|
import '../services/api_client.dart';
|
||||||
|
|
||||||
|
/// 钱包、金币解锁(09 收费支付)。
|
||||||
|
class WalletRepository {
|
||||||
|
WalletRepository(this._client);
|
||||||
|
|
||||||
|
final ApiClient _client;
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> fetchWallet() async {
|
||||||
|
final data = await _client.get('/app/wallet');
|
||||||
|
return data as Map<String, dynamic>;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> unlockEpisode(int episodeId) async {
|
||||||
|
final data = await _client.post('/app/episodes/$episodeId/unlock');
|
||||||
|
return data as Map<String, dynamic>;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<dynamic>> fetchOrders() async {
|
||||||
|
final data = await _client.get('/app/orders');
|
||||||
|
return data as List;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
// Author: fruit.com.cn
|
||||||
|
|
||||||
|
import '../services/api_client.dart';
|
||||||
|
|
||||||
|
/// 观看记录与收藏(A5)。
|
||||||
|
class WatchRepository {
|
||||||
|
WatchRepository(this._client);
|
||||||
|
|
||||||
|
final ApiClient _client;
|
||||||
|
|
||||||
|
Future<void> reportProgress({
|
||||||
|
required int episodeId,
|
||||||
|
required int positionSec,
|
||||||
|
required int durationSec,
|
||||||
|
}) async {
|
||||||
|
await _client.post('/app/watch/progress', data: {
|
||||||
|
'episodeId': episodeId,
|
||||||
|
'positionSec': positionSec,
|
||||||
|
'durationSec': durationSec,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> fetchProgress(int dramaId) async {
|
||||||
|
final data = await _client.get('/app/watch/progress', query: {
|
||||||
|
'dramaId': dramaId,
|
||||||
|
});
|
||||||
|
return data as Map<String, dynamic>;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<dynamic>> fetchRecent({int limit = 20}) async {
|
||||||
|
final data = await _client.get('/app/watch/recent', query: {'limit': limit});
|
||||||
|
return data as List;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteProgress(int dramaId) async {
|
||||||
|
await _client.delete('/app/watch/progress/$dramaId');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> clearProgress() async {
|
||||||
|
await _client.delete('/app/watch/progress');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> toggleFavorite(int dramaId) async {
|
||||||
|
final data = await _client.post('/app/favorites/toggle', data: {
|
||||||
|
'dramaId': dramaId,
|
||||||
|
});
|
||||||
|
final map = data as Map<String, dynamic>;
|
||||||
|
return map['favorited'] as bool? ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<dynamic>> fetchFavorites() async {
|
||||||
|
final data = await _client.get('/app/favorites');
|
||||||
|
return data as List;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteFavorite(int dramaId) async {
|
||||||
|
await _client.delete('/app/favorites/$dramaId');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,39 @@
|
|||||||
|
// Author: fruit.com.cn
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
import '../models/app_catalog.dart';
|
||||||
|
import '../repositories/http_catalog_repository.dart';
|
||||||
|
|
||||||
|
/// 首页/发现页 feed 状态(A3 数据层)。
|
||||||
|
class AppCatalogController extends ChangeNotifier {
|
||||||
|
AppCatalogController(this._repository);
|
||||||
|
|
||||||
|
final HttpCatalogRepository _repository;
|
||||||
|
|
||||||
|
AppFeed? feed;
|
||||||
|
bool loading = false;
|
||||||
|
String? error;
|
||||||
|
|
||||||
|
Future<void> loadFeed() async {
|
||||||
|
loading = true;
|
||||||
|
error = null;
|
||||||
|
notifyListeners();
|
||||||
|
try {
|
||||||
|
feed = await _repository.fetchHomeFeed();
|
||||||
|
} catch (e) {
|
||||||
|
error = e.toString();
|
||||||
|
} finally {
|
||||||
|
loading = false;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<AppDramaDetail> loadDetail(int id) {
|
||||||
|
return _repository.fetchDramaDetail(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<AppCategory>> loadCategories() {
|
||||||
|
return _repository.fetchCategories();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,64 @@
|
|||||||
|
// Author: fruit.com.cn
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'api_client.dart';
|
||||||
|
|
||||||
|
/// 核心埋点事件名(第一阶段口径,后续与运营对齐后可扩展)。
|
||||||
|
class AppEvents {
|
||||||
|
AppEvents._();
|
||||||
|
|
||||||
|
static const String playbackStart = 'playback_start';
|
||||||
|
static const String playbackEnd = 'playback_end';
|
||||||
|
static const String paySuccess = 'pay_success';
|
||||||
|
static const String register = 'register';
|
||||||
|
static const String click = 'click';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 埋点上报:本地缓冲、批量上报,失败静默丢弃不影响主流程。
|
||||||
|
class EventTracker {
|
||||||
|
EventTracker(this._api);
|
||||||
|
|
||||||
|
final ApiClient _api;
|
||||||
|
|
||||||
|
static const int _flushSize = 10;
|
||||||
|
static const Duration _flushInterval = Duration(seconds: 10);
|
||||||
|
|
||||||
|
final List<Map<String, Object?>> _buffer = [];
|
||||||
|
Timer? _timer;
|
||||||
|
|
||||||
|
void track(
|
||||||
|
String eventName, {
|
||||||
|
String? page,
|
||||||
|
Map<String, Object?>? properties,
|
||||||
|
}) {
|
||||||
|
_buffer.add({
|
||||||
|
'eventName': eventName,
|
||||||
|
'eventTime': DateTime.now().toIso8601String(),
|
||||||
|
'page': page,
|
||||||
|
'properties': properties,
|
||||||
|
});
|
||||||
|
if (_buffer.length >= _flushSize) {
|
||||||
|
flush();
|
||||||
|
} else {
|
||||||
|
_timer ??= Timer(_flushInterval, flush);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> flush() async {
|
||||||
|
if (_buffer.isEmpty) return;
|
||||||
|
final events = List<Map<String, Object?>>.from(_buffer);
|
||||||
|
_buffer.clear();
|
||||||
|
_timer?.cancel();
|
||||||
|
_timer = null;
|
||||||
|
try {
|
||||||
|
await _api.post('/app/events', data: {
|
||||||
|
'deviceId': 'device-0001',
|
||||||
|
'sessionId': 'session-0001',
|
||||||
|
'events': events,
|
||||||
|
});
|
||||||
|
} catch (_) {
|
||||||
|
// 埋点失败不影响主流程,静默丢弃。
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,468 @@
|
|||||||
|
// 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 / 手机号 / 邮箱 三种登录入口 + 游客浏览。
|
||||||
|
class LoginPage extends StatefulWidget {
|
||||||
|
const LoginPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<LoginPage> createState() => _LoginPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LoginPageState extends State<LoginPage> {
|
||||||
|
static const _orange = Color(0xFFFF5B3D);
|
||||||
|
static const _purple = Color(0xFF9278EA);
|
||||||
|
|
||||||
|
void _enterHome() {
|
||||||
|
Navigator.of(context).pushReplacement(
|
||||||
|
MaterialPageRoute<void>(builder: (_) => const HomePage()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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(
|
||||||
|
body: SafeArea(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
// 顶部:关闭 + 游客浏览
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
_IconButton(icon: Icons.close, onTap: () {}),
|
||||||
|
TextButton(
|
||||||
|
onPressed: _enterHome,
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor: Colors.white54,
|
||||||
|
backgroundColor: Colors.white.withValues(alpha: 0.05),
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 14,
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
|
shape: const StadiumBorder(),
|
||||||
|
),
|
||||||
|
child: const Text('Guest Explore'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// 中央内容
|
||||||
|
Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 28),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const _BrandLogo(),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
const Text(
|
||||||
|
'DramaPulse',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 30,
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
const Text(
|
||||||
|
'Sign in to sync your progress and coins',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.white54,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 36),
|
||||||
|
_GoogleButton(onTap: _enterHome),
|
||||||
|
const SizedBox(height: 14),
|
||||||
|
_DarkButton(
|
||||||
|
icon: Icons.phone_iphone,
|
||||||
|
iconColor: _orange,
|
||||||
|
label: 'Continue with Phone Number',
|
||||||
|
onTap: () => _showLoginSheet('PHONE'),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 14),
|
||||||
|
_DarkButton(
|
||||||
|
icon: Icons.email_outlined,
|
||||||
|
iconColor: _purple,
|
||||||
|
label: 'Continue with Email',
|
||||||
|
onTap: () => _showLoginSheet('EMAIL'),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 32),
|
||||||
|
const _BonusPill(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// 底部协议
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: 24),
|
||||||
|
child: Text(
|
||||||
|
'By continuing, you agree to DramaPulse\'s\n'
|
||||||
|
'Terms of Service · Privacy Policy · Content Policy',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(fontSize: 11, color: Colors.white38),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _IconButton extends StatelessWidget {
|
||||||
|
const _IconButton({required this.icon, required this.onTap});
|
||||||
|
|
||||||
|
final IconData icon;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return InkWell(
|
||||||
|
onTap: onTap,
|
||||||
|
borderRadius: BorderRadius.circular(24),
|
||||||
|
child: Container(
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white.withValues(alpha: 0.05),
|
||||||
|
border: Border.all(color: Colors.white12),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: Icon(icon, color: Colors.white54, size: 20),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BrandLogo extends StatelessWidget {
|
||||||
|
const _BrandLogo();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: const LinearGradient(
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
colors: [Color(0xFFFF5B3D), Color(0xFFD8536C), Color(0xFF9278EA)],
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
border: Border.all(color: Colors.white24),
|
||||||
|
),
|
||||||
|
child: const Icon(Icons.play_arrow_rounded, color: Colors.white, size: 40),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _GoogleButton extends StatelessWidget {
|
||||||
|
const _GoogleButton({required this.onTap});
|
||||||
|
|
||||||
|
final VoidCallback onTap;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _AuthButton(
|
||||||
|
onTap: onTap,
|
||||||
|
background: Colors.white,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const _GoogleG(),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Text(
|
||||||
|
'Continue with Google',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.grey.shade900,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 简化版 Google「G」logo。
|
||||||
|
class _GoogleG extends StatelessWidget {
|
||||||
|
const _GoogleG();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: Color(0xFF4285F4),
|
||||||
|
),
|
||||||
|
child: const Center(
|
||||||
|
child: Text(
|
||||||
|
'G',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DarkButton extends StatelessWidget {
|
||||||
|
const _DarkButton({
|
||||||
|
required this.icon,
|
||||||
|
required this.iconColor,
|
||||||
|
required this.label,
|
||||||
|
required this.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
|
final IconData icon;
|
||||||
|
final Color iconColor;
|
||||||
|
final String label;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _AuthButton(
|
||||||
|
onTap: onTap,
|
||||||
|
background: const Color(0xFF161822),
|
||||||
|
border: const Color(0xFF232634),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(icon, color: iconColor, size: 20),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AuthButton extends StatelessWidget {
|
||||||
|
const _AuthButton({
|
||||||
|
required this.onTap,
|
||||||
|
required this.background,
|
||||||
|
required this.child,
|
||||||
|
this.border,
|
||||||
|
});
|
||||||
|
|
||||||
|
final VoidCallback onTap;
|
||||||
|
final Color background;
|
||||||
|
final Color? border;
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 52,
|
||||||
|
child: Material(
|
||||||
|
color: background,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
side: border == null ? BorderSide.none : BorderSide(color: border!),
|
||||||
|
),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: onTap,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BonusPill extends StatelessWidget {
|
||||||
|
const _BonusPill();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white.withValues(alpha: 0.04),
|
||||||
|
border: Border.all(color: Colors.white12),
|
||||||
|
borderRadius: BorderRadius.circular(999),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 6,
|
||||||
|
height: 6,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Color(0xFF34D399),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
const Text(
|
||||||
|
'Get ',
|
||||||
|
style: TextStyle(fontSize: 11, color: Colors.white54),
|
||||||
|
),
|
||||||
|
const Text(
|
||||||
|
'100 Free Bonus Coins',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Text(
|
||||||
|
' upon first sign-in',
|
||||||
|
style: TextStyle(fontSize: 11, color: Colors.white54),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+115
-75
@@ -6,7 +6,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: archive
|
name: archive
|
||||||
sha256: "0d58d1fa1fddf6588251ccf2819f3fb3b6d0452c1b2813ac7413a05592bfbefe"
|
sha256: "0d58d1fa1fddf6588251ccf2819f3fb3b6d0452c1b2813ac7413a05592bfbefe"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.5.2"
|
version: "4.5.2"
|
||||||
args:
|
args:
|
||||||
@@ -14,7 +14,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: args
|
name: args
|
||||||
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
|
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.7.0"
|
version: "2.7.0"
|
||||||
async:
|
async:
|
||||||
@@ -22,7 +22,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: async
|
name: async
|
||||||
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
|
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.13.1"
|
version: "2.13.1"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
@@ -30,7 +30,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: boolean_selector
|
name: boolean_selector
|
||||||
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "2.1.2"
|
||||||
characters:
|
characters:
|
||||||
@@ -38,7 +38,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: characters
|
name: characters
|
||||||
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.1"
|
version: "1.4.1"
|
||||||
checked_yaml:
|
checked_yaml:
|
||||||
@@ -46,7 +46,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: checked_yaml
|
name: checked_yaml
|
||||||
sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f"
|
sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.4"
|
version: "2.0.4"
|
||||||
cli_util:
|
cli_util:
|
||||||
@@ -54,23 +54,23 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: cli_util
|
name: cli_util
|
||||||
sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
|
sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.2"
|
version: "0.4.2"
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: clock
|
name: clock
|
||||||
sha256: e51d50bca3217c9a9fa2b41a30e4a38971133f5f9ec7a3d57bae095007f1d28e
|
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.3"
|
version: "1.1.2"
|
||||||
collection:
|
collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: collection
|
name: collection
|
||||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.19.1"
|
version: "1.19.1"
|
||||||
csslib:
|
csslib:
|
||||||
@@ -78,7 +78,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: csslib
|
name: csslib
|
||||||
sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e"
|
sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.2"
|
version: "1.0.2"
|
||||||
cupertino_icons:
|
cupertino_icons:
|
||||||
@@ -86,15 +86,31 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: cupertino_icons
|
name: cupertino_icons
|
||||||
sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd"
|
sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.9"
|
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:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: fake_async
|
name: fake_async
|
||||||
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.3"
|
version: "1.3.3"
|
||||||
ffi:
|
ffi:
|
||||||
@@ -102,7 +118,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: ffi
|
name: ffi
|
||||||
sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45"
|
sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.2.0"
|
||||||
file:
|
file:
|
||||||
@@ -110,7 +126,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: file
|
name: file
|
||||||
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.0.1"
|
version: "7.0.1"
|
||||||
flutter:
|
flutter:
|
||||||
@@ -123,7 +139,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: flutter_launcher_icons
|
name: flutter_launcher_icons
|
||||||
sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7"
|
sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.14.4"
|
version: "0.14.4"
|
||||||
flutter_lints:
|
flutter_lints:
|
||||||
@@ -131,7 +147,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: flutter_lints
|
name: flutter_lints
|
||||||
sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
|
sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.0"
|
version: "6.0.0"
|
||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
@@ -154,31 +170,39 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: html
|
name: html
|
||||||
sha256: "43b67b8f43321ab066817dfac5619596c98bb1b61624e77203bb4351785f9699"
|
sha256: "43b67b8f43321ab066817dfac5619596c98bb1b61624e77203bb4351785f9699"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.15.7"
|
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:
|
image:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image
|
name: image
|
||||||
sha256: "89ed75b2f8231fe676808c5564f9590d728024ded7ac782a7ed8474f1c95b72a"
|
sha256: "89ed75b2f8231fe676808c5564f9590d728024ded7ac782a7ed8474f1c95b72a"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.12.0"
|
version: "4.12.0"
|
||||||
intl:
|
intl:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: intl
|
name: intl
|
||||||
sha256: "1ca20c894b1717686a2319b8548763d812bc0aabdac580420a44c5178c57a867"
|
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.20.3"
|
version: "0.20.2"
|
||||||
json_annotation:
|
json_annotation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: json_annotation
|
name: json_annotation
|
||||||
sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80"
|
sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.12.0"
|
version: "4.12.0"
|
||||||
leak_tracker:
|
leak_tracker:
|
||||||
@@ -186,7 +210,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: leak_tracker
|
name: leak_tracker
|
||||||
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "11.0.2"
|
version: "11.0.2"
|
||||||
leak_tracker_flutter_testing:
|
leak_tracker_flutter_testing:
|
||||||
@@ -194,7 +218,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: leak_tracker_flutter_testing
|
name: leak_tracker_flutter_testing
|
||||||
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.10"
|
version: "3.0.10"
|
||||||
leak_tracker_testing:
|
leak_tracker_testing:
|
||||||
@@ -202,7 +226,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: leak_tracker_testing
|
name: leak_tracker_testing
|
||||||
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.2"
|
version: "3.0.2"
|
||||||
lints:
|
lints:
|
||||||
@@ -210,39 +234,47 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: lints
|
name: lints
|
||||||
sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df"
|
sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.0"
|
version: "6.1.0"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: matcher
|
name: matcher
|
||||||
sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd"
|
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.20"
|
version: "0.12.19"
|
||||||
material_color_utilities:
|
material_color_utilities:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.13.0"
|
version: "0.13.0"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9"
|
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
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:
|
nested:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: nested
|
name: nested
|
||||||
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
|
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.0.0"
|
||||||
path:
|
path:
|
||||||
@@ -250,7 +282,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: path
|
name: path
|
||||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.1"
|
version: "1.9.1"
|
||||||
path_provider_linux:
|
path_provider_linux:
|
||||||
@@ -258,7 +290,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: path_provider_linux
|
name: path_provider_linux
|
||||||
sha256: "58c2005f147315b11e9b4a7bc889cd5203e250cba8e3f012dae259b4972b5c16"
|
sha256: "58c2005f147315b11e9b4a7bc889cd5203e250cba8e3f012dae259b4972b5c16"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.2"
|
version: "2.2.2"
|
||||||
path_provider_platform_interface:
|
path_provider_platform_interface:
|
||||||
@@ -266,7 +298,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: path_provider_platform_interface
|
name: path_provider_platform_interface
|
||||||
sha256: "484838772624c3a4b94f1e44a3e19897fee738f2d5c4ce448443b0417f7c9dda"
|
sha256: "484838772624c3a4b94f1e44a3e19897fee738f2d5c4ce448443b0417f7c9dda"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.3"
|
version: "2.1.3"
|
||||||
path_provider_windows:
|
path_provider_windows:
|
||||||
@@ -274,7 +306,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: path_provider_windows
|
name: path_provider_windows
|
||||||
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.0"
|
version: "2.3.0"
|
||||||
platform:
|
platform:
|
||||||
@@ -282,7 +314,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: platform
|
name: platform
|
||||||
sha256: a36d119c13416516a7b5913fbe8af8531e11633d784c550b2125f76c758524ec
|
sha256: a36d119c13416516a7b5913fbe8af8531e11633d784c550b2125f76c758524ec
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.2.0"
|
version: "3.2.0"
|
||||||
plugin_platform_interface:
|
plugin_platform_interface:
|
||||||
@@ -290,7 +322,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: plugin_platform_interface
|
name: plugin_platform_interface
|
||||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.8"
|
version: "2.1.8"
|
||||||
posix:
|
posix:
|
||||||
@@ -298,7 +330,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: posix
|
name: posix
|
||||||
sha256: bc1bad54ad2b735816e31f8d4600cfde6c7839975085ddfbca48b6c9f7c4044e
|
sha256: bc1bad54ad2b735816e31f8d4600cfde6c7839975085ddfbca48b6c9f7c4044e
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.5.2"
|
version: "6.5.2"
|
||||||
provider:
|
provider:
|
||||||
@@ -306,7 +338,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: provider
|
name: provider
|
||||||
sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272"
|
sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.5+1"
|
version: "6.1.5+1"
|
||||||
shared_preferences:
|
shared_preferences:
|
||||||
@@ -314,7 +346,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: shared_preferences
|
name: shared_preferences
|
||||||
sha256: "1e85e0dccf5b4a016077fea7dfbb4a7cb39c759ac1596fcf60c06deadb7da91f"
|
sha256: "1e85e0dccf5b4a016077fea7dfbb4a7cb39c759ac1596fcf60c06deadb7da91f"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.6"
|
version: "2.5.6"
|
||||||
shared_preferences_android:
|
shared_preferences_android:
|
||||||
@@ -322,7 +354,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: shared_preferences_android
|
name: shared_preferences_android
|
||||||
sha256: "1e12aafe408aa50da80edfd679a2a6bf63ba7ab37c7fa98286da459a757b3399"
|
sha256: "1e12aafe408aa50da80edfd679a2a6bf63ba7ab37c7fa98286da459a757b3399"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.28"
|
version: "2.4.28"
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
@@ -330,7 +362,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: shared_preferences_foundation
|
name: shared_preferences_foundation
|
||||||
sha256: "2ec3934efa51e46117f23031cc141b8fc878e8525b94ec1ea4f7f586cf1b47ea"
|
sha256: "2ec3934efa51e46117f23031cc141b8fc878e8525b94ec1ea4f7f586cf1b47ea"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.7"
|
version: "2.5.7"
|
||||||
shared_preferences_linux:
|
shared_preferences_linux:
|
||||||
@@ -338,7 +370,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: shared_preferences_linux
|
name: shared_preferences_linux
|
||||||
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
|
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.1"
|
version: "2.4.1"
|
||||||
shared_preferences_platform_interface:
|
shared_preferences_platform_interface:
|
||||||
@@ -346,7 +378,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: shared_preferences_platform_interface
|
name: shared_preferences_platform_interface
|
||||||
sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9"
|
sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.2"
|
version: "2.4.2"
|
||||||
shared_preferences_web:
|
shared_preferences_web:
|
||||||
@@ -354,7 +386,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: shared_preferences_web
|
name: shared_preferences_web
|
||||||
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
|
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.3"
|
version: "2.4.3"
|
||||||
shared_preferences_windows:
|
shared_preferences_windows:
|
||||||
@@ -362,7 +394,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: shared_preferences_windows
|
name: shared_preferences_windows
|
||||||
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
|
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.1"
|
version: "2.4.1"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
@@ -375,23 +407,23 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: source_span
|
name: source_span
|
||||||
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
|
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.10.2"
|
version: "1.10.2"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: stack_trace
|
name: stack_trace
|
||||||
sha256: "277654b3034d17ac6f9f1cb5595db011b1d5d41e8806866db28e0abaa101c490"
|
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.12.2"
|
version: "1.12.1"
|
||||||
stream_channel:
|
stream_channel:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: stream_channel
|
name: stream_channel
|
||||||
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
version: "2.1.4"
|
||||||
string_scanner:
|
string_scanner:
|
||||||
@@ -399,7 +431,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: string_scanner
|
name: string_scanner
|
||||||
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.1"
|
version: "1.4.1"
|
||||||
term_glyph:
|
term_glyph:
|
||||||
@@ -407,31 +439,39 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: term_glyph
|
name: term_glyph
|
||||||
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.2"
|
version: "1.2.2"
|
||||||
test_api:
|
test_api:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "2a122cbe059f8b610d3a5415f42e255b6c17b1f21eee1d960f31080237fb4f11"
|
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
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:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vector_math
|
name: vector_math
|
||||||
sha256: "92b9910f66ed1057fd4da7b040ae7c74cafacf885bdc81be496928d5049b032d"
|
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.3"
|
version: "2.2.0"
|
||||||
video_player:
|
video_player:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: video_player
|
name: video_player
|
||||||
sha256: "07ae1e03f2d4c8e2e9a75ee678f90acbc4506d4458a9c628512d119a1eee5754"
|
sha256: "07ae1e03f2d4c8e2e9a75ee678f90acbc4506d4458a9c628512d119a1eee5754"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.14.1"
|
version: "2.14.1"
|
||||||
video_player_android:
|
video_player_android:
|
||||||
@@ -439,7 +479,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: video_player_android
|
name: video_player_android
|
||||||
sha256: d27054dea34d748a44f06d433a57005d2aac69944485b0abbaed3303dbcfa3f1
|
sha256: d27054dea34d748a44f06d433a57005d2aac69944485b0abbaed3303dbcfa3f1
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.12.2"
|
version: "2.12.2"
|
||||||
video_player_avfoundation:
|
video_player_avfoundation:
|
||||||
@@ -447,7 +487,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: video_player_avfoundation
|
name: video_player_avfoundation
|
||||||
sha256: "4f8a845173377ab250a50f9b9dfe4a43500cc76a7c49dd8410d02d8fc29dfd8b"
|
sha256: "4f8a845173377ab250a50f9b9dfe4a43500cc76a7c49dd8410d02d8fc29dfd8b"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.12.1"
|
version: "2.12.1"
|
||||||
video_player_platform_interface:
|
video_player_platform_interface:
|
||||||
@@ -455,7 +495,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: video_player_platform_interface
|
name: video_player_platform_interface
|
||||||
sha256: "92c0fbabe20c788e71fd10d26cea998d0d253282e65d145aed0818731cf593ce"
|
sha256: "92c0fbabe20c788e71fd10d26cea998d0d253282e65d145aed0818731cf593ce"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.9.0"
|
version: "6.9.0"
|
||||||
video_player_web:
|
video_player_web:
|
||||||
@@ -463,23 +503,23 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: video_player_web
|
name: video_player_web
|
||||||
sha256: "9f3c00be2ef9b76a95d94ac5119fb843dca6f2c69e6c9968f6f2b6c9e7afbdeb"
|
sha256: "9f3c00be2ef9b76a95d94ac5119fb843dca6f2c69e6c9968f6f2b6c9e7afbdeb"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.0"
|
version: "2.4.0"
|
||||||
vm_service:
|
vm_service:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vm_service
|
name: vm_service
|
||||||
sha256: "5f37239c4851efcef929cea7824e76df7f2f0970aef85d66bbc430afa40e72f0"
|
sha256: "89b11d62e1040ee681af3b4f1c97a803ecbf010c6e8b9c5896ee5b08c3bf7b78"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "15.3.0"
|
version: "15.4.0"
|
||||||
web:
|
web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: web
|
name: web
|
||||||
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.1.1"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
@@ -487,7 +527,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: xdg_directories
|
name: xdg_directories
|
||||||
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.0"
|
||||||
yaml:
|
yaml:
|
||||||
@@ -495,7 +535,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: yaml
|
name: yaml
|
||||||
sha256: f67cdd8e07d3c6329146aaef1ba043542b3134c12489f553ca9a7435d1068aea
|
sha256: f67cdd8e07d3c6329146aaef1ba043542b3134c12489f553ca9a7435d1068aea
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.4"
|
version: "3.1.4"
|
||||||
sdks:
|
sdks:
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ dependencies:
|
|||||||
provider: ^6.1.5+1
|
provider: ^6.1.5+1
|
||||||
video_player: ^2.14.0
|
video_player: ^2.14.0
|
||||||
shared_preferences: ^2.5.5
|
shared_preferences: ^2.5.5
|
||||||
|
dio: ^5.7.0
|
||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user