diff --git a/lib/models/search_result.dart b/lib/models/search_result.dart index aeeebf23..9a820a47 100644 --- a/lib/models/search_result.dart +++ b/lib/models/search_result.dart @@ -150,10 +150,9 @@ class SearchStartEvent extends SearchEvent { SearchStartEvent({ required this.query, required this.totalSources, - required int timestamp, + required super.timestamp, }) : super( type: SearchEventType.start, - timestamp: timestamp, ); factory SearchStartEvent.fromJson(Map json) { @@ -175,10 +174,9 @@ class SearchSourceResultEvent extends SearchEvent { required this.source, required this.sourceName, required this.results, - required int timestamp, + required super.timestamp, }) : super( type: SearchEventType.sourceResult, - timestamp: timestamp, ); factory SearchSourceResultEvent.fromJson(Map json) { @@ -206,10 +204,9 @@ class SearchSourceErrorEvent extends SearchEvent { required this.source, required this.sourceName, required this.error, - required int timestamp, + required super.timestamp, }) : super( type: SearchEventType.sourceError, - timestamp: timestamp, ); factory SearchSourceErrorEvent.fromJson(Map json) { @@ -230,10 +227,9 @@ class SearchCompleteEvent extends SearchEvent { SearchCompleteEvent({ required this.totalResults, required this.completedSources, - required int timestamp, + required super.timestamp, }) : super( type: SearchEventType.complete, - timestamp: timestamp, ); factory SearchCompleteEvent.fromJson(Map json) { diff --git a/lib/screens/anime_screen.dart b/lib/screens/anime_screen.dart index 132adac5..9c8d25f5 100644 --- a/lib/screens/anime_screen.dart +++ b/lib/screens/anime_screen.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:google_fonts/google_fonts.dart'; import 'package:provider/provider.dart'; import '../services/theme_service.dart'; import '../widgets/capsule_tab_switcher.dart'; diff --git a/lib/screens/live_player_screen.dart b/lib/screens/live_player_screen.dart index 87ffb110..647b9f9e 100644 --- a/lib/screens/live_player_screen.dart +++ b/lib/screens/live_player_screen.dart @@ -433,10 +433,12 @@ class _LivePlayerScreenState extends State children: [ Column( children: [ - // Windows 自定义标题栏 + // Windows 自定义标题栏(跟随主题) if (Platform.isWindows) - const WindowsTitleBar( - customBackgroundColor: Color(0xFF000000), + WindowsTitleBar( + customBackgroundColor: isDarkMode + ? const Color(0xFF121212) + : const Color(0xFFf5f5f5), ), // 主要内容 Expanded( @@ -1798,8 +1800,8 @@ class _LivePlayerScreenState extends State Container( width: 4, height: 4, - decoration: BoxDecoration( - color: const Color(0xFF27ae60), + decoration: const BoxDecoration( + color: Color(0xFF27ae60), shape: BoxShape.circle, ), ), diff --git a/lib/screens/login_screen.dart b/lib/screens/login_screen.dart index c2fbe4cc..9b437793 100644 --- a/lib/screens/login_screen.dart +++ b/lib/screens/login_screen.dart @@ -28,6 +28,11 @@ class _LoginScreenState extends State { bool _isLoading = false; bool _isFormValid = false; bool _isLocalMode = false; + bool _showAdvancedSettings = false; + bool _enableBrowserHeaders = false; + final _userAgentController = TextEditingController(); + final _customHeaderNameController = TextEditingController(); + final _customHeaderValueController = TextEditingController(); // 点击计数器相关 int _logoTapCount = 0; @@ -41,6 +46,23 @@ class _LoginScreenState extends State { _passwordController.addListener(_validateForm); _subscriptionUrlController.addListener(_validateForm); _loadSavedUserData(); + _loadAdvancedSettings(); + } + + void _loadAdvancedSettings() async { + final ua = await UserDataService.getCustomUserAgent(); + final enabled = await UserDataService.getEnableBrowserHeaders(); + final header = await UserDataService.getCustomHeader(); + if (mounted) { + setState(() { + _userAgentController.text = ua; + _enableBrowserHeaders = enabled; + if (header.isNotEmpty) { + _customHeaderNameController.text = header.keys.first; + _customHeaderValueController.text = header.values.first; + } + }); + } } void _loadSavedUserData() async { @@ -82,6 +104,9 @@ class _LoginScreenState extends State { _usernameController.dispose(); _passwordController.dispose(); _subscriptionUrlController.dispose(); + _userAgentController.dispose(); + _customHeaderNameController.dispose(); + _customHeaderValueController.dispose(); _tapTimer?.cancel(); super.dispose(); } @@ -257,16 +282,23 @@ class _LoginScreenState extends State { } String _parseCookies(http.Response response) { - // 解析 Set-Cookie 头部 - List cookies = []; - - // 获取所有 Set-Cookie 头部 - final setCookieHeaders = response.headers['set-cookie']; - if (setCookieHeaders != null) { - // HTTP 头部通常是 String 类型 - final cookieParts = setCookieHeaders.split(';'); - if (cookieParts.isNotEmpty) { - cookies.add(cookieParts[0].trim()); + final allCookies = response.headers['set-cookie']; + if (allCookies == null || allCookies.isEmpty) return ''; + + final lines = allCookies + .split('\n') + .expand((line) => line.split(',')) + .where((s) => s.trim().isNotEmpty) + .toList(); + + final cookies = []; + for (final line in lines) { + final semicolonIdx = line.indexOf(';'); + final nameValue = semicolonIdx > 0 + ? line.substring(0, semicolonIdx).trim() + : line.trim(); + if (nameValue.contains('=')) { + cookies.add(nameValue); } } @@ -301,16 +333,37 @@ class _LoginScreenState extends State { }); try { + // 保存高级设置 + await UserDataService.saveCustomUserAgent(_userAgentController.text); + await UserDataService.saveEnableBrowserHeaders(_enableBrowserHeaders); + await UserDataService.saveCustomHeader( + _customHeaderNameController.text, + _customHeaderValueController.text); + // 处理 URL String baseUrl = _processUrl(_urlController.text); String loginUrl = '$baseUrl/api/login'; + final loginHeaders = { + 'Content-Type': 'application/json', + }; + loginHeaders['User-Agent'] = _userAgentController.text; + if (_enableBrowserHeaders) { + loginHeaders['Accept-Language'] = 'zh-CN,zh;q=0.9,en;q=0.8'; + loginHeaders['Sec-Fetch-Site'] = 'same-origin'; + loginHeaders['Sec-Fetch-Mode'] = 'cors'; + loginHeaders['Sec-Fetch-Dest'] = 'empty'; + } + if (_customHeaderNameController.text.isNotEmpty && + _customHeaderValueController.text.isNotEmpty) { + loginHeaders[_customHeaderNameController.text.trim()] = + _customHeaderValueController.text.trim(); + } + // 发送登录请求 final response = await http.post( Uri.parse(loginUrl), - headers: { - 'Content-Type': 'application/json', - }, + headers: loginHeaders, body: json.encode({ 'username': _usernameController.text, 'password': _passwordController.text, @@ -502,6 +555,168 @@ class _LoginScreenState extends State { } } + Widget _buildAdvancedSettingsToggle() { + return Padding( + padding: const EdgeInsets.only(top: 12), + child: GestureDetector( + onTap: () { + setState(() { + _showAdvancedSettings = !_showAdvancedSettings; + }); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + _showAdvancedSettings + ? Icons.expand_less + : Icons.expand_more, + color: const Color(0xFF7f8c8d), + size: 18, + ), + const SizedBox(width: 4), + Text( + '高级设置', + style: FontUtils.poppins( + fontSize: 13, + color: const Color(0xFF7f8c8d), + ), + ), + ], + ), + ), + ); + } + + Widget _buildAdvancedSettingsPanel() { + return Padding( + padding: const EdgeInsets.only(top: 12), + child: Container( + decoration: BoxDecoration( + color: Colors.white.withValues(alpha: 0.4), + borderRadius: BorderRadius.circular(12), + ), + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + 'User-Agent', + style: FontUtils.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: const Color(0xFF2c3e50), + ), + ), + const Spacer(), + SizedBox( + height: 20, + child: Switch.adaptive( + value: _enableBrowserHeaders, + onChanged: (v) { + setState(() { + _enableBrowserHeaders = v; + }); + }, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + ), + Text( + '浏览器头', + style: FontUtils.poppins( + fontSize: 12, + color: const Color(0xFF7f8c8d), + ), + ), + ], + ), + const SizedBox(height: 8), + TextFormField( + controller: _userAgentController, + maxLines: 2, + minLines: 1, + style: FontUtils.poppins( + fontSize: 12, + color: const Color(0xFF2c3e50), + ), + decoration: InputDecoration( + hintText: '自定义 User-Agent', + hintStyle: FontUtils.poppins( + fontSize: 12, + color: const Color(0xFFbdc3c7), + ), + filled: true, + fillColor: Colors.white.withValues(alpha: 0.5), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide.none, + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 10), + ), + ), + const SizedBox(height: 12), + Row( + children: [ + Expanded( + child: TextFormField( + controller: _customHeaderNameController, + style: FontUtils.poppins( + fontSize: 12, + color: const Color(0xFF2c3e50), + ), + decoration: InputDecoration( + hintText: '自定义头名称', + hintStyle: FontUtils.poppins( + fontSize: 12, + color: const Color(0xFFbdc3c7), + ), + filled: true, + fillColor: Colors.white.withValues(alpha: 0.5), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide.none, + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 10), + ), + ), + ), + const SizedBox(width: 8), + Expanded( + child: TextFormField( + controller: _customHeaderValueController, + style: FontUtils.poppins( + fontSize: 12, + color: const Color(0xFF2c3e50), + ), + decoration: InputDecoration( + hintText: '自定义头值', + hintStyle: FontUtils.poppins( + fontSize: 12, + color: const Color(0xFFbdc3c7), + ), + filled: true, + fillColor: Colors.white.withValues(alpha: 0.5), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide.none, + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 10), + ), + ), + ), + ], + ), + ], + ), + ), + ); + } + @override Widget build(BuildContext context) { final isTablet = DeviceUtils.isTablet(context); @@ -775,7 +990,7 @@ class _LoginScreenState extends State { ? Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - SizedBox( + const SizedBox( height: 18, width: 18, child: CircularProgressIndicator( @@ -805,6 +1020,9 @@ class _LoginScreenState extends State { ), ), ), + // 高级设置折叠面板 + _buildAdvancedSettingsToggle(), + if (_showAdvancedSettings) _buildAdvancedSettingsPanel(), ], ), ), @@ -1072,6 +1290,9 @@ class _LoginScreenState extends State { ), ), ), + // 高级设置折叠面板 + _buildAdvancedSettingsToggle(), + if (_showAdvancedSettings) _buildAdvancedSettingsPanel(), ], ), ), diff --git a/lib/screens/player_screen.dart b/lib/screens/player_screen.dart index c0e45455..15f4fd05 100644 --- a/lib/screens/player_screen.dart +++ b/lib/screens/player_screen.dart @@ -545,7 +545,7 @@ class _PlayerScreenState extends State // 异步保存播放记录(不等待结果) PageCacheService().savePlayRecord(playRecord, context).then((_) { debugPrint( - '保存播放进度 [场景: $scene]: source: $currentSourceSnapshot, id: $currentIDSnapshot, 第${currentEpisodeIndexSnapshot + 1}集, 时间: ${playTime}秒'); + '保存播放进度 [场景: $scene]: source: $currentSourceSnapshot, id: $currentIDSnapshot, 第${currentEpisodeIndexSnapshot + 1}集, 时间: $playTime秒'); }).catchError((e) { debugPrint('保存播放进度失败 [场景: $scene]: $e'); }); @@ -1165,7 +1165,7 @@ class _PlayerScreenState extends State // 等待下一帧,确保 MobileVideoPlayerWidget 已经重新创建 WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted && currentDetail != null) { - debugPrint('恢复播放: 第${resumeEpisodeIndex + 1}集, ${resumeSeconds}秒'); + debugPrint('恢复播放: 第${resumeEpisodeIndex + 1}集, $resumeSeconds秒'); // 调用 startPlay 重新初始化播放器 startPlay(resumeEpisodeIndex, resumeSeconds); } @@ -1435,12 +1435,12 @@ class _PlayerScreenState extends State return LayoutBuilder( builder: (context, constraints) { final double screenWidth = constraints.maxWidth; - final double padding = 16.0; - final double spacing = 12.0; + const double padding = 16.0; + const double spacing = 12.0; final crossAxisCount = _isTablet ? 6 : 3; final double availableWidth = screenWidth - (padding * 2) - (spacing * (crossAxisCount - 1)); - final double minItemWidth = 80.0; + const double minItemWidth = 80.0; final double calculatedItemWidth = availableWidth / crossAxisCount; final double itemWidth = math.max(calculatedItemWidth, minItemWidth); final double itemHeight = itemWidth * 2.0; @@ -1639,7 +1639,7 @@ class _PlayerScreenState extends State builder: (context, constraints) { // 计算按钮宽度:根据设备类型调整 final screenWidth = constraints.maxWidth; - final horizontalPadding = 32.0; // 左右各16 + const horizontalPadding = 32.0; // 左右各16 final availableWidth = screenWidth - horizontalPadding; final cardsPerView = _isTablet ? 6.2 : 3.2; final buttonWidth = (availableWidth / cardsPerView) - 6; // 减去右边距6 @@ -1800,7 +1800,7 @@ class _PlayerScreenState extends State builder: (context) { return StatefulBuilder( builder: (BuildContext context, StateSetter setState) { - return Container( + return SizedBox( height: panelHeight, width: double.infinity, child: PlayerEpisodesPanel( @@ -1901,7 +1901,7 @@ class _PlayerScreenState extends State builder: (context) { return StatefulBuilder( builder: (BuildContext context, StateSetter setState) { - return Container( + return SizedBox( height: panelHeight, width: double.infinity, child: PlayerDetailsPanel( @@ -2038,7 +2038,7 @@ class _PlayerScreenState extends State builder: (context, constraints) { // 计算卡片宽度:根据设备类型调整 final screenWidth = constraints.maxWidth; - final horizontalPadding = 32.0; // 左右各16 + const horizontalPadding = 32.0; // 左右各16 final availableWidth = screenWidth - horizontalPadding; final cardsPerView = _isTablet ? 6.2 : 3.2; final cardWidth = (availableWidth / cardsPerView) - 6; // 减去右边距6 @@ -2168,7 +2168,7 @@ class _PlayerScreenState extends State builder: (context) { return StatefulBuilder( builder: (BuildContext context, StateSetter setState) { - return Container( + return SizedBox( height: panelHeight, width: double.infinity, child: PlayerSourcesPanel( @@ -2619,10 +2619,12 @@ class _PlayerScreenState extends State ), child: Column( children: [ - // Windows 自定义标题栏(播放页使用纯黑背景) + // Windows 自定义标题栏(播放页跟随主题) if (Platform.isWindows) - const WindowsTitleBar( - customBackgroundColor: Color(0xFF000000), + WindowsTitleBar( + customBackgroundColor: isDarkMode + ? const Color(0xFF121212) + : const Color(0xFFf5f5f5), ), // 主要内容 Expanded( diff --git a/lib/screens/search_screen.dart b/lib/screens/search_screen.dart index 1fffbebb..aa45f50b 100644 --- a/lib/screens/search_screen.dart +++ b/lib/screens/search_screen.dart @@ -38,7 +38,7 @@ class _SearchScreenState extends State final ScrollController _scrollController = ScrollController(); String _searchQuery = ''; List _searchHistory = []; - List _searchResults = []; + final List _searchResults = []; bool _hasSearched = false; bool _hasReceivedStart = false; // 是否已收到start消息 String? _searchError; diff --git a/lib/services/api_service.dart b/lib/services/api_service.dart index 5b0eddc7..02c16339 100644 --- a/lib/services/api_service.dart +++ b/lib/services/api_service.dart @@ -73,6 +73,30 @@ class ApiService { return '$cleanBaseUrl$cleanEndpoint'; } + /// 添加浏览器特征头 + static Future _addBrowserHeaders(Map headers) async { + final ua = await UserDataService.getCustomUserAgent(); + headers['User-Agent'] = ua; + + final enabled = await UserDataService.getEnableBrowserHeaders(); + if (enabled) { + headers['Accept-Language'] = 'zh-CN,zh;q=0.9,en;q=0.8'; + headers['Accept-Encoding'] = 'gzip, deflate, br'; + headers['Sec-Fetch-Site'] = 'same-origin'; + headers['Sec-Fetch-Mode'] = 'cors'; + headers['Sec-Fetch-Dest'] = 'empty'; + headers['Sec-Ch-Ua'] = + '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"'; + headers['Sec-Ch-Ua-Mobile'] = '?0'; + headers['Sec-Ch-Ua-Platform'] = '"Windows"'; + } + + final customHeader = await UserDataService.getCustomHeader(); + if (customHeader.isNotEmpty) { + headers.addAll(customHeader); + } + } + /// 构建请求头 static Future> _buildHeaders({ Map? additionalHeaders, @@ -83,6 +107,9 @@ class ApiService { 'Accept': 'application/json', }; + // 添加浏览器特征头 + await _addBrowserHeaders(headers); + // 添加认证cookies if (includeAuth) { final cookies = await _getCookies(); @@ -574,13 +601,16 @@ class ApiService { } String loginUrl = '$baseUrl/api/login'; + final loginHeaders = { + 'Content-Type': 'application/json', + }; + await _addBrowserHeaders(loginHeaders); + // 发送登录请求 final response = await http .post( Uri.parse(loginUrl), - headers: { - 'Content-Type': 'application/json', - }, + headers: loginHeaders, body: json.encode({ 'username': username, 'password': password, @@ -802,17 +832,26 @@ class ApiService { } } - /// 解析 Set-Cookie 头部 + /// 解析 Set-Cookie 头部,提取所有 Cookie 的 name=value static String _parseCookies(http.Response response) { - List cookies = []; - - // 获取所有 Set-Cookie 头部 - final setCookieHeaders = response.headers['set-cookie']; - if (setCookieHeaders != null) { - // HTTP 头部通常是 String 类型 - final cookieParts = setCookieHeaders.split(';'); - if (cookieParts.isNotEmpty) { - cookies.add(cookieParts[0].trim()); + final allCookies = response.headers['set-cookie']; + if (allCookies == null || allCookies.isEmpty) return ''; + + // 按 \n(Dart http 包多值拼接方式)或 ","(HTTP 标准)切分多个 cookie + final lines = allCookies + .split('\n') + .expand((line) => line.split(',')) + .where((s) => s.trim().isNotEmpty) + .toList(); + + final cookies = []; + for (final line in lines) { + final semicolonIdx = line.indexOf(';'); + final nameValue = semicolonIdx > 0 + ? line.substring(0, semicolonIdx).trim() + : line.trim(); + if (nameValue.contains('=')) { + cookies.add(nameValue); } } diff --git a/lib/services/douban_service.dart b/lib/services/douban_service.dart index a69b889d..22597550 100644 --- a/lib/services/douban_service.dart +++ b/lib/services/douban_service.dart @@ -203,9 +203,9 @@ class DoubanService { // 为了保持与现有代码的兼容性,将时长转换为字符串 String? duration; if (episodeLength != null) { - duration = '${episodeLength}分钟'; + duration = '$episodeLength分钟'; } else if (movieDuration != null) { - duration = '${movieDuration}分钟'; + duration = '$movieDuration分钟'; } // 提取剧情简介 - 两个正则都匹配,选择内容更长的 diff --git a/lib/services/local_search_cache_service.dart b/lib/services/local_search_cache_service.dart index 66f0af1e..e7b86d3b 100644 --- a/lib/services/local_search_cache_service.dart +++ b/lib/services/local_search_cache_service.dart @@ -141,7 +141,7 @@ class LocalSearchCacheService { if (_cleanupTimer != null) return; // 避免重复启动 _cleanupTimer = Timer.periodic( - Duration(milliseconds: _cacheCleanupIntervalMs), + const Duration(milliseconds: _cacheCleanupIntervalMs), (_) { _performCacheCleanup(); }, diff --git a/lib/services/page_cache_service.dart b/lib/services/page_cache_service.dart index 1a673edc..8fd7f75b 100644 --- a/lib/services/page_cache_service.dart +++ b/lib/services/page_cache_service.dart @@ -554,7 +554,7 @@ class PageCacheService final existingItem = cachedData[existingIndex]; final updatedHistory = [ existingItem, - ...cachedData.where((item) => item != query).toList() + ...cachedData.where((item) => item != query) ]; setCache(cacheKey, updatedHistory); } diff --git a/lib/services/sse_search_service.dart b/lib/services/sse_search_service.dart index 8dac40b3..c2a4931a 100644 --- a/lib/services/sse_search_service.dart +++ b/lib/services/sse_search_service.dart @@ -270,7 +270,7 @@ class SSESearchService { _buffer = ''; // 使用流式 UTF-8 解码器,自动处理跨 chunk 的多字节字符 - final utf8Decoder = const Utf8Decoder(allowMalformed: false); + const utf8Decoder = Utf8Decoder(allowMalformed: false); // 流式处理 SSE 数据 await for (final chunk in response.stream.transform(utf8Decoder)) { diff --git a/lib/services/user_data_service.dart b/lib/services/user_data_service.dart index 7e89e160..b93695e4 100644 --- a/lib/services/user_data_service.dart +++ b/lib/services/user_data_service.dart @@ -11,6 +11,10 @@ class UserDataService { static const String _preferSpeedTestKey = 'prefer_speed_test'; static const String _localSearchKey = 'local_search'; static const String _isLocalModeKey = 'is_local_mode'; + static const String _customUserAgentKey = 'custom_user_agent'; + static const String _enableBrowserHeadersKey = 'enable_browser_headers'; + static const String _customHeaderNameKey = 'custom_header_name'; + static const String _customHeaderValueKey = 'custom_header_value'; // 内存缓存 static bool? _isLocalModeCache; @@ -257,4 +261,45 @@ class UserDataService { static bool getIsLocalModeSync() { return _isLocalModeCache ?? false; } + + static const String _defaultUserAgent = + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' + 'AppleWebKit/537.36 (KHTML, like Gecko) ' + 'Chrome/124.0.0.0 Safari/537.36'; + + static Future saveCustomUserAgent(String ua) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setString(_customUserAgentKey, ua); + } + + static Future getCustomUserAgent() async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getString(_customUserAgentKey) ?? _defaultUserAgent; + } + + static Future saveEnableBrowserHeaders(bool enabled) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setBool(_enableBrowserHeadersKey, enabled); + } + + static Future getEnableBrowserHeaders() async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getBool(_enableBrowserHeadersKey) ?? false; + } + + static Future saveCustomHeader(String name, String value) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setString(_customHeaderNameKey, name); + await prefs.setString(_customHeaderValueKey, value); + } + + static Future> getCustomHeader() async { + final prefs = await SharedPreferences.getInstance(); + final name = prefs.getString(_customHeaderNameKey) ?? ''; + final value = prefs.getString(_customHeaderValueKey) ?? ''; + if (name.isNotEmpty && value.isNotEmpty) { + return {name: value}; + } + return {}; + } } diff --git a/lib/services/version_service.dart b/lib/services/version_service.dart index a9415bcc..63f79835 100644 --- a/lib/services/version_service.dart +++ b/lib/services/version_service.dart @@ -81,7 +81,7 @@ class VersionService { // 检查上次检查时间(每天最多提示一次) final lastCheck = prefs.getInt(_lastCheckKey) ?? 0; final now = DateTime.now().millisecondsSinceEpoch; - final dayInMs = 24 * 60 * 60 * 1000; + const dayInMs = 24 * 60 * 60 * 1000; if (now - lastCheck < dayInMs) { return false; diff --git a/lib/widgets/continue_watching_section.dart b/lib/widgets/continue_watching_section.dart index a9f1c7fc..d89f082d 100644 --- a/lib/widgets/continue_watching_section.dart +++ b/lib/widgets/continue_watching_section.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../models/play_record.dart'; import '../models/video_info.dart'; -import '../services/api_service.dart'; import '../services/page_cache_service.dart'; import '../services/theme_service.dart'; import '../utils/device_utils.dart'; diff --git a/lib/widgets/dlna_player_controls.dart b/lib/widgets/dlna_player_controls.dart index ffc1c35e..b15a182a 100644 --- a/lib/widgets/dlna_player_controls.dart +++ b/lib/widgets/dlna_player_controls.dart @@ -304,16 +304,16 @@ class _DLNAPlayerControlsState extends State { ), ), // 中央加载指示器 - Center( + const Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ - const CircularProgressIndicator( + CircularProgressIndicator( color: Colors.white, strokeWidth: 3, ), - const SizedBox(height: 16), - const Text( + SizedBox(height: 16), + Text( '视频加载中...', style: TextStyle( color: Colors.white, diff --git a/lib/widgets/favorites_grid.dart b/lib/widgets/favorites_grid.dart index 419326a6..0c31772c 100644 --- a/lib/widgets/favorites_grid.dart +++ b/lib/widgets/favorites_grid.dart @@ -349,10 +349,10 @@ class _FavoritesGridState extends State child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon( + const Icon( Icons.error_outline, size: 80, - color: const Color(0xFFbdc3c7), + color: Color(0xFFbdc3c7), ), const SizedBox(height: 24), Text( diff --git a/lib/widgets/mobile_player_controls.dart b/lib/widgets/mobile_player_controls.dart index a5bd2fb6..eee0ac8b 100644 --- a/lib/widgets/mobile_player_controls.dart +++ b/lib/widgets/mobile_player_controls.dart @@ -244,8 +244,9 @@ class _MobilePlayerControlsState extends State { } void _onSwipeUpdate(DragUpdateDetails details) { - if (_isLocked || !_isSeekingViaSwipe || widget.live || _screenSize == null) + if (_isLocked || !_isSeekingViaSwipe || widget.live || _screenSize == null) { return; + } final screenWidth = _screenSize!.width; final swipeDistance = details.globalPosition.dx - _swipeStartX; final swipeRatio = swipeDistance / (screenWidth * 0.5); diff --git a/lib/widgets/player_details_panel.dart b/lib/widgets/player_details_panel.dart index f0408d39..261cc364 100644 --- a/lib/widgets/player_details_panel.dart +++ b/lib/widgets/player_details_panel.dart @@ -168,7 +168,7 @@ class PlayerDetailsPanel extends StatelessWidget { ], if (totalEpisodes != null && totalEpisodes > 1) ...[ Text( - '全${totalEpisodes}集', + '全$totalEpisodes集', style: theme.textTheme.bodyMedium?.copyWith( color: isDarkMode ? Colors.grey[400] @@ -364,7 +364,7 @@ class PlayerDetailsPanel extends StatelessWidget { const SizedBox(height: 4), if (totalEpisodes > 1) Text( - '全${totalEpisodes}集', + '全$totalEpisodes集', style: theme.textTheme.bodyMedium?.copyWith( color: isDarkMode ? Colors.grey[400] diff --git a/lib/widgets/search_result_agg_grid.dart b/lib/widgets/search_result_agg_grid.dart index 5316176e..306d4828 100644 --- a/lib/widgets/search_result_agg_grid.dart +++ b/lib/widgets/search_result_agg_grid.dart @@ -158,10 +158,10 @@ class _SearchResultAggGridState extends State child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon( + const Icon( Icons.search_off, size: 80, - color: const Color(0xFFbdc3c7), + color: Color(0xFFbdc3c7), ), const SizedBox(height: 24), Text( diff --git a/lib/widgets/user_menu.dart b/lib/widgets/user_menu.dart index f2f6cb03..f8e3d582 100644 --- a/lib/widgets/user_menu.dart +++ b/lib/widgets/user_menu.dart @@ -850,10 +850,10 @@ class _UserMenuState extends State { ), child: Row( children: [ - Icon( + const Icon( LucideIcons.trash2, size: 20, - color: const Color(0xFFf59e0b), + color: Color(0xFFf59e0b), ), const SizedBox(width: 12), Text( @@ -890,10 +890,10 @@ class _UserMenuState extends State { ), child: Row( children: [ - Icon( + const Icon( LucideIcons.download, size: 20, - color: const Color(0xFF3b82f6), + color: Color(0xFF3b82f6), ), const SizedBox(width: 12), Text( diff --git a/lib/widgets/video_menu_bottom_sheet.dart b/lib/widgets/video_menu_bottom_sheet.dart index df956867..eaeb317d 100644 --- a/lib/widgets/video_menu_bottom_sheet.dart +++ b/lib/widgets/video_menu_bottom_sheet.dart @@ -68,7 +68,7 @@ class CollapsibleScrollPhysics extends ScrollPhysics { @override ScrollPhysics buildParent(ScrollPhysics? ancestor) { // 根据平台选择合适的父物理效果 - final parentPhysics = isIOS ? BouncingScrollPhysics() : ClampingScrollPhysics(); + final parentPhysics = isIOS ? const BouncingScrollPhysics() : const ClampingScrollPhysics(); return parent?.applyTo(ancestor ?? parentPhysics) ?? parentPhysics; } } @@ -803,7 +803,7 @@ class _VideoMenuBottomSheetState extends State // 关闭按钮 GestureDetector( onTap: widget.onClose, - child: Container( + child: SizedBox( width: 32, height: 32, child: Icon( @@ -938,10 +938,10 @@ class _VideoMenuBottomSheetState extends State child: Row( mainAxisSize: MainAxisSize.min, children: [ - Icon( + const Icon( Icons.star, size: 16, - color: const Color(0xFFFFB800), + color: Color(0xFFFFB800), ), const SizedBox(width: 4), Text( @@ -1102,10 +1102,10 @@ class _VideoMenuBottomSheetState extends State child: Row( mainAxisSize: MainAxisSize.min, children: [ - Icon( + const Icon( Icons.star, size: 16, - color: const Color(0xFFE91E63), + color: Color(0xFFE91E63), ), const SizedBox(width: 4), Text( diff --git a/lib/widgets/video_player_widget.dart b/lib/widgets/video_player_widget.dart index 29e38375..fea486c1 100644 --- a/lib/widgets/video_player_widget.dart +++ b/lib/widgets/video_player_widget.dart @@ -168,7 +168,13 @@ class _VideoPlayerWidgetState extends State if (_playerDisposed) { return; } - _player = Player(); + _player = Player( + configuration: PlayerConfiguration( + title: 'Selene', + bufferSize: 32 * 1024 * 1024, + logLevel: MPVLogLevel.error, + ), + ); _videoController = VideoController(_player!); _setupPlayerListeners(); if (_currentUrl != null) { diff --git a/lib/widgets/windows_title_bar.dart b/lib/widgets/windows_title_bar.dart index c1a31eb7..a672da66 100644 --- a/lib/widgets/windows_title_bar.dart +++ b/lib/widgets/windows_title_bar.dart @@ -170,7 +170,7 @@ class _WindowsButtonHoverState extends State<_WindowsButtonHover> { color: backgroundColor ?? Colors.transparent, child: Center( child: widget.isCloseButton && _isHovered - ? Icon( + ? const Icon( Icons.close, size: 16, color: Colors.white,