From 82bb3e2f1437856b9813bef8fb020913d706d629 Mon Sep 17 00:00:00 2001 From: Developer Date: Fri, 12 Jun 2026 22:30:26 +0800 Subject: [PATCH] =?UTF-8?q?=E9=B8=BF=E8=92=99=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/presentation/register_section.dart | 46 +- .../presentation/register_step_account.dart | 141 ++++- .../presentation/register_step_password.dart | 140 +---- .../pages/tool/rss_reader_page.dart | 524 ++++++++++++++---- .../font_management_notifier.dart | 7 +- 5 files changed, 589 insertions(+), 269 deletions(-) diff --git a/lib/features/auth/presentation/register_section.dart b/lib/features/auth/presentation/register_section.dart index 4353e01e..e699c8db 100644 --- a/lib/features/auth/presentation/register_section.dart +++ b/lib/features/auth/presentation/register_section.dart @@ -109,13 +109,13 @@ class _RegisterSectionState extends ConsumerState parent: _stepAnimController, curve: Curves.easeOut, ); - _stepSlideAnimation = Tween( - begin: const Offset(0.15, 0), - end: Offset.zero, - ).animate(CurvedAnimation( - parent: _stepAnimController, - curve: Curves.easeOutCubic, - )); + _stepSlideAnimation = + Tween(begin: const Offset(0.15, 0), end: Offset.zero).animate( + CurvedAnimation( + parent: _stepAnimController, + curve: Curves.easeOutCubic, + ), + ); _stepAnimController.value = 1.0; } @@ -239,11 +239,8 @@ class _RegisterSectionState extends ConsumerState ), ), GestureDetector( - onTap: () => RegisterDialogs.showRegisterTips( - context, - ext: ext, - auth: auth, - ), + onTap: () => + RegisterDialogs.showRegisterTips(context, ext: ext, auth: auth), child: Icon( CupertinoIcons.info_circle, size: 22, @@ -301,6 +298,10 @@ class _RegisterSectionState extends ConsumerState _goToStep(2); _sendCode(_emailController.text.trim()); }, + agreedToTerms: _agreedToTerms, + onToggleAgreement: () => + setState(() => _agreedToTerms = !_agreedToTerms), + onShowAgreement: _showAgreement, ext: ext, auth: auth, ); @@ -372,15 +373,12 @@ class _RegisterSectionState extends ConsumerState subscribeEmail: _subscribeEmail, onToggleSubscribe: () => setState(() => _subscribeEmail = !_subscribeEmail), - agreedToTerms: _agreedToTerms, - onToggleAgreement: () => - setState(() => _agreedToTerms = !_agreedToTerms), - onShowAgreement: _showAgreement, onPrev: () => _goToStep(2), onBack: () => _goToStep(1), onRegister: _handleRegister, isLoading: authState.isLoading, - canRegister: _agreedToTerms && + canRegister: + _agreedToTerms && _regPasswordController.text.isNotEmpty && _regConfirmPasswordController.text.isNotEmpty && !authState.isLoading, @@ -495,7 +493,12 @@ class _RegisterSectionState extends ConsumerState } /// 处理注册错误 - void _handleRegisterError(Object e, String email, String username, String password) { + void _handleRegisterError( + Object e, + String email, + String username, + String password, + ) { final errorMsg = e.toString(); if (errorMsg.contains('已注册') || errorMsg.contains('already') || @@ -619,10 +622,9 @@ class _RegisterSectionState extends ConsumerState ) async { final t = ref.read(translationsProvider); try { - final success = await ref.read(authProvider.notifier).login( - account: username, - password: password, - ); + final success = await ref + .read(authProvider.notifier) + .login(account: username, password: password); if (success && mounted) { AppToast.showSuccess(t.auth.registerSuccess); widget.onRegisterSuccess(); diff --git a/lib/features/auth/presentation/register_step_account.dart b/lib/features/auth/presentation/register_step_account.dart index 90854ca0..45dc79a1 100644 --- a/lib/features/auth/presentation/register_step_account.dart +++ b/lib/features/auth/presentation/register_step_account.dart @@ -1,9 +1,9 @@ /// ============================================================ /// 闲言APP — 注册步骤1:账号邮箱输入 /// 创建时间: 2026-06-08 -/// 更新时间: 2026-06-08 -/// 作用: 注册流程第一步,输入用户名和邮箱,含格式校验和可用性检测 -/// 上次更新: 增加用户名格式校验、实时可用性防抖检测 +/// 更新时间: 2026-06-12 +/// 作用: 注册流程第一步,输入用户名和邮箱,含格式校验、可用性检测及协议勾选 +/// 上次更新: 增加用户协议勾选框,勾选后才允许进入下一步 /// ============================================================ import 'dart:async'; @@ -40,13 +40,16 @@ enum UsernameStatus { taken, } -/// 注册步骤1 — 账号邮箱输入 +/// 注册步骤1 — 账号邮箱输入(含协议勾选) class RegisterStepAccount extends StatefulWidget { const RegisterStepAccount({ super.key, required this.usernameController, required this.emailController, required this.onNext, + required this.agreedToTerms, + required this.onToggleAgreement, + required this.onShowAgreement, required this.ext, required this.auth, }); @@ -54,6 +57,9 @@ class RegisterStepAccount extends StatefulWidget { final TextEditingController usernameController; final TextEditingController emailController; final VoidCallback onNext; + final bool agreedToTerms; + final VoidCallback onToggleAgreement; + final void Function(bool isUserAgreement) onShowAgreement; final AppThemeExtension ext; final TAuth auth; @@ -143,7 +149,9 @@ class _RegisterStepAccountState extends State { final taken = await UserSecurityService.checkUsername(username); if (!mounted) return; setState(() { - _usernameStatus = taken ? UsernameStatus.taken : UsernameStatus.available; + _usernameStatus = taken + ? UsernameStatus.taken + : UsernameStatus.available; _usernameError = taken ? auth.usernameTaken : ''; }); } catch (e) { @@ -157,14 +165,15 @@ class _RegisterStepAccountState extends State { } } - /// 是否可以进入下一步 + /// 是否可以进入下一步(需填写完整 + 用户名合法 + 已勾选协议) bool get _canProceed { final username = widget.usernameController.text.trim(); final email = widget.emailController.text.trim(); return username.isNotEmpty && email.isNotEmpty && _usernameStatus != UsernameStatus.invalid && - _usernameStatus != UsernameStatus.taken; + _usernameStatus != UsernameStatus.taken && + widget.agreedToTerms; } /// 点击下一步 @@ -172,6 +181,12 @@ class _RegisterStepAccountState extends State { final username = widget.usernameController.text.trim(); final email = widget.emailController.text.trim(); + // 协议勾选校验 + if (!widget.agreedToTerms) { + AppToast.showWarning(auth.pleaseAgreeTerms); + return; + } + // 二次格式校验 if (username.isEmpty || email.isEmpty) { AppToast.showWarning(auth.pleaseFillRequired); @@ -220,6 +235,9 @@ class _RegisterStepAccountState extends State { keyboardType: TextInputType.emailAddress, ), const SizedBox(height: AppSpacing.lg), + // 协议同意行 + _buildAgreementRow(), + const SizedBox(height: AppSpacing.lg), // 下一步按钮 SizedBox( width: double.infinity, @@ -272,9 +290,7 @@ class _RegisterStepAccountState extends State { const SizedBox(width: AppSpacing.sm), Text( auth.usernameChecking, - style: AppTypography.caption1.copyWith( - color: ext.textSecondary, - ), + style: AppTypography.caption1.copyWith(color: ext.textSecondary), ), ], ), @@ -288,14 +304,16 @@ class _RegisterStepAccountState extends State { padding: const EdgeInsets.only(top: AppSpacing.xs), child: Row( children: [ - Icon(CupertinoIcons.xmark_circle_fill, size: 14, color: ext.errorColor), + Icon( + CupertinoIcons.xmark_circle_fill, + size: 14, + color: ext.errorColor, + ), const SizedBox(width: AppSpacing.sm), Expanded( child: Text( _usernameError, - style: AppTypography.caption1.copyWith( - color: ext.errorColor, - ), + style: AppTypography.caption1.copyWith(color: ext.errorColor), ), ), ], @@ -309,13 +327,15 @@ class _RegisterStepAccountState extends State { padding: const EdgeInsets.only(top: AppSpacing.xs), child: Row( children: [ - Icon(CupertinoIcons.checkmark_circle_fill, size: 14, color: ext.successColor), + Icon( + CupertinoIcons.checkmark_circle_fill, + size: 14, + color: ext.successColor, + ), const SizedBox(width: AppSpacing.sm), Text( auth.username, - style: AppTypography.caption1.copyWith( - color: ext.successColor, - ), + style: AppTypography.caption1.copyWith(color: ext.successColor), ), ], ), @@ -324,4 +344,89 @@ class _RegisterStepAccountState extends State { return const SizedBox.shrink(); } + + /// 协议同意行(用户协议 + 隐私政策) + Widget _buildAgreementRow() { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: widget.onToggleAgreement, + child: ConstrainedBox( + constraints: const BoxConstraints(minWidth: 44, minHeight: 44), + child: Center( + child: Container( + width: 20, + height: 20, + margin: const EdgeInsets.only(top: 1), + decoration: BoxDecoration( + color: widget.agreedToTerms + ? ext.accent + : CupertinoColors.transparent, + borderRadius: BorderRadius.circular(5), + border: Border.all( + color: widget.agreedToTerms ? ext.accent : ext.textHint, + width: 1.5, + ), + ), + child: widget.agreedToTerms + ? Icon( + CupertinoIcons.checkmark, + size: 13, + color: ext.textOnAccent, + ) + : null, + ), + ), + ), + ), + const SizedBox(width: AppSpacing.sm), + Expanded( + child: Text.rich( + TextSpan( + text: auth.registerAgreePrefix, + style: AppTypography.footnote.copyWith(color: ext.textSecondary), + children: [ + WidgetSpan( + child: GestureDetector( + onTap: () => widget.onShowAgreement(true), + child: Text( + auth.userAgreement.startsWith('\u300a') + ? auth.userAgreement + : '\u300a${auth.userAgreement}\u300b', + style: AppTypography.footnote.copyWith( + color: ext.accent, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + TextSpan( + text: auth.and, + style: AppTypography.footnote.copyWith( + color: ext.textSecondary, + ), + ), + WidgetSpan( + child: GestureDetector( + onTap: () => widget.onShowAgreement(false), + child: Text( + auth.privacyPolicy.startsWith('\u300a') + ? auth.privacyPolicy + : '\u300a${auth.privacyPolicy}\u300b', + style: AppTypography.footnote.copyWith( + color: ext.accent, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ], + ), + ), + ), + ], + ); + } } diff --git a/lib/features/auth/presentation/register_step_password.dart b/lib/features/auth/presentation/register_step_password.dart index 5724dcab..b5811942 100644 --- a/lib/features/auth/presentation/register_step_password.dart +++ b/lib/features/auth/presentation/register_step_password.dart @@ -1,13 +1,12 @@ /// ============================================================ /// 闲言APP — 注册步骤3:设置密码与安全选项 /// 创建时间: 2026-06-08 -/// 更新时间: 2026-06-08 -/// 作用: 注册流程第三步,设置密码、密保问题、订阅协议,完成注册 -/// 上次更新: 从register_section.dart分流创建 +/// 更新时间: 2026-06-12 +/// 作用: 注册流程第三步,设置密码、密保问题、订阅邮件,完成注册 +/// 上次更新: 移除协议勾选(已移至步骤1) /// ============================================================ import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; import '../../../../core/theme/app_theme.dart'; import '../../../../core/theme/app_spacing.dart'; @@ -36,9 +35,6 @@ class RegisterStepPassword extends StatelessWidget { required this.onSelectSecQuestion, required this.subscribeEmail, required this.onToggleSubscribe, - required this.agreedToTerms, - required this.onToggleAgreement, - required this.onShowAgreement, required this.onPrev, required this.onBack, required this.onRegister, @@ -63,9 +59,6 @@ class RegisterStepPassword extends StatelessWidget { final VoidCallback onSelectSecQuestion; final bool subscribeEmail; final VoidCallback onToggleSubscribe; - final bool agreedToTerms; - final VoidCallback onToggleAgreement; - final void Function(bool isUserAgreement) onShowAgreement; final VoidCallback onPrev; final VoidCallback onBack; final VoidCallback onRegister; @@ -90,15 +83,30 @@ class RegisterStepPassword extends StatelessWidget { ), child: Row( children: [ - Icon(CupertinoIcons.person_crop_circle_fill, size: 32, color: ext.accent), + Icon( + CupertinoIcons.person_crop_circle_fill, + size: 32, + color: ext.accent, + ), const SizedBox(width: AppSpacing.sm), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(username, style: AppTypography.subhead.copyWith(color: ext.textPrimary, fontWeight: FontWeight.w600)), + Text( + username, + style: AppTypography.subhead.copyWith( + color: ext.textPrimary, + fontWeight: FontWeight.w600, + ), + ), const SizedBox(height: 2), - Text(email, style: AppTypography.caption1.copyWith(color: ext.textSecondary)), + Text( + email, + style: AppTypography.caption1.copyWith( + color: ext.textSecondary, + ), + ), ], ), ), @@ -124,7 +132,9 @@ class RegisterStepPassword extends StatelessWidget { constraints: const BoxConstraints(minWidth: 44, minHeight: 44), child: Center( child: Icon( - obscurePassword ? CupertinoIcons.eye_slash : CupertinoIcons.eye, + obscurePassword + ? CupertinoIcons.eye_slash + : CupertinoIcons.eye, size: 18, color: ext.textHint, ), @@ -158,9 +168,6 @@ class RegisterStepPassword extends StatelessWidget { // 邮件订阅 _buildSubscribeRow(), const SizedBox(height: AppSpacing.lg), - // 协议 - _buildAgreementRow(), - const SizedBox(height: AppSpacing.md), // 导航按钮 _buildNavButtons(), ], @@ -256,11 +263,7 @@ class RegisterStepPassword extends StatelessWidget { ), ), ), - Icon( - CupertinoIcons.chevron_right, - size: 14, - color: ext.textHint, - ), + Icon(CupertinoIcons.chevron_right, size: 14, color: ext.textHint), ], ), ); @@ -276,9 +279,7 @@ class RegisterStepPassword extends StatelessWidget { child: CupertinoTextField( controller: secAnswerController, placeholder: auth.enterSecAnswerHint, - placeholderStyle: AppTypography.body.copyWith( - color: ext.textHint, - ), + placeholderStyle: AppTypography.body.copyWith(color: ext.textHint), style: AppTypography.body.copyWith(color: ext.textPrimary), decoration: null, padding: const EdgeInsets.symmetric( @@ -319,100 +320,13 @@ class RegisterStepPassword extends StatelessWidget { const SizedBox(width: AppSpacing.sm), Text( auth.subscribeEmail, - style: AppTypography.footnote.copyWith( - color: ext.textSecondary, - ), + style: AppTypography.footnote.copyWith(color: ext.textSecondary), ), ], ), ); } - /// 协议同意行 - Widget _buildAgreementRow() { - return Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: onToggleAgreement, - child: ConstrainedBox( - constraints: const BoxConstraints(minWidth: 44, minHeight: 44), - child: Center( - child: Container( - width: 20, - height: 20, - margin: const EdgeInsets.only(top: 1), - decoration: BoxDecoration( - color: agreedToTerms ? ext.accent : Colors.transparent, - borderRadius: BorderRadius.circular(5), - border: Border.all( - color: agreedToTerms ? ext.accent : ext.textHint, - width: 1.5, - ), - ), - child: agreedToTerms - ? Icon( - CupertinoIcons.checkmark, - size: 13, - color: ext.textOnAccent, - ) - : null, - ), - ), - ), - ), - const SizedBox(width: AppSpacing.sm), - Expanded( - child: Text.rich( - TextSpan( - text: auth.registerAgreePrefix, - style: AppTypography.footnote.copyWith( - color: ext.textSecondary, - ), - children: [ - WidgetSpan( - child: GestureDetector( - onTap: () => onShowAgreement(true), - child: Text( - auth.userAgreement.startsWith('\u300a') - ? auth.userAgreement - : '\u300a${auth.userAgreement}\u300b', - style: AppTypography.footnote.copyWith( - color: ext.accent, - fontWeight: FontWeight.w600, - ), - ), - ), - ), - TextSpan( - text: auth.and, - style: AppTypography.footnote.copyWith( - color: ext.textSecondary, - ), - ), - WidgetSpan( - child: GestureDetector( - onTap: () => onShowAgreement(false), - child: Text( - auth.privacyPolicy.startsWith('\u300a') - ? auth.privacyPolicy - : '\u300a${auth.privacyPolicy}\u300b', - style: AppTypography.footnote.copyWith( - color: ext.accent, - fontWeight: FontWeight.w600, - ), - ), - ), - ), - ], - ), - ), - ), - ], - ); - } - /// 导航按钮行(上一步 + 完成注册) Widget _buildNavButtons() { return Row( diff --git a/lib/features/discover/presentation/pages/tool/rss_reader_page.dart b/lib/features/discover/presentation/pages/tool/rss_reader_page.dart index 47d76a73..97ee332c 100644 --- a/lib/features/discover/presentation/pages/tool/rss_reader_page.dart +++ b/lib/features/discover/presentation/pages/tool/rss_reader_page.dart @@ -83,12 +83,21 @@ class _RssReaderPageState extends ConsumerState { Expanded( child: SafeArea( child: switch (state.currentView) { - RssView.subscriptions => - _buildSubscriptionHome(ext, state, notifier), - RssView.articleList => - _buildArticleList(ext, state, notifier), - RssView.articleDetail => - _buildArticleDetail(ext, state, notifier), + RssView.subscriptions => _buildSubscriptionHome( + ext, + state, + notifier, + ), + RssView.articleList => _buildArticleList( + ext, + state, + notifier, + ), + RssView.articleDetail => _buildArticleDetail( + ext, + state, + notifier, + ), }, ), ), @@ -106,7 +115,11 @@ class _RssReaderPageState extends ConsumerState { RssView.subscriptions => '📡 RSS订阅', }; - Widget _buildLeading(AppThemeExtension ext, RssState state, RssNotifier notifier) { + Widget _buildLeading( + AppThemeExtension ext, + RssState state, + RssNotifier notifier, + ) { if (state.currentView == RssView.articleDetail) { return CupertinoButton( padding: EdgeInsets.zero, @@ -124,15 +137,25 @@ class _RssReaderPageState extends ConsumerState { return const AdaptiveBackButton(); } - Widget _buildTrailing(AppThemeExtension ext, RssState state, RssNotifier notifier) { + Widget _buildTrailing( + AppThemeExtension ext, + RssState state, + RssNotifier notifier, + ) { // 文章详情 — 朗读按钮 if (state.currentView == RssView.articleDetail) { return GestureDetector( onTap: () => _speakArticle(state), child: Container( - width: 32, height: 32, - decoration: BoxDecoration(color: ext.bgSecondary, borderRadius: AppRadius.mdBorder), - child: const Center(child: Text('🔊', style: TextStyle(fontSize: 14))), + width: 32, + height: 32, + decoration: BoxDecoration( + color: ext.bgSecondary, + borderRadius: AppRadius.mdBorder, + ), + child: const Center( + child: Text('🔊', style: TextStyle(fontSize: 14)), + ), ), ); } @@ -144,13 +167,18 @@ class _RssReaderPageState extends ConsumerState { GestureDetector( onTap: notifier.toggleCardMode, child: Container( - width: 32, height: 32, + width: 32, + height: 32, decoration: BoxDecoration( - color: state.isCardMode ? ext.accent.withValues(alpha: 0.12) : ext.bgSecondary, + color: state.isCardMode + ? ext.accent.withValues(alpha: 0.12) + : ext.bgSecondary, borderRadius: AppRadius.mdBorder, ), child: Icon( - state.isCardMode ? CupertinoIcons.list_bullet : CupertinoIcons.square_grid_2x2, + state.isCardMode + ? CupertinoIcons.list_bullet + : CupertinoIcons.square_grid_2x2, size: 16, color: state.isCardMode ? ext.accent : ext.textHint, ), @@ -172,7 +200,8 @@ class _RssReaderPageState extends ConsumerState { GestureDetector( onTap: _showAddSubscriptionSheet, child: Container( - width: 32, height: 32, + width: 32, + height: 32, decoration: BoxDecoration( color: ext.accent.withValues(alpha: 0.1), borderRadius: AppRadius.mdBorder, @@ -187,7 +216,10 @@ class _RssReaderPageState extends ConsumerState { /// 网络标识 Widget _buildNetworkBadge(AppThemeExtension ext) { return Container( - padding: const EdgeInsets.symmetric(horizontal: AppSpacing.sm, vertical: AppSpacing.xs), + padding: const EdgeInsets.symmetric( + horizontal: AppSpacing.sm, + vertical: AppSpacing.xs, + ), decoration: BoxDecoration( color: ext.accent.withValues(alpha: 0.1), borderRadius: AppRadius.mdBorder, @@ -206,15 +238,24 @@ class _RssReaderPageState extends ConsumerState { /// 网络异常提示条 Widget _buildNetworkWarningBar() { return Container( - padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md, vertical: AppSpacing.xs), + padding: const EdgeInsets.symmetric( + horizontal: AppSpacing.md, + vertical: AppSpacing.xs, + ), color: CupertinoColors.systemRed.withValues(alpha: 0.1), child: Row( children: [ - const Icon(CupertinoIcons.wifi_exclamationmark, size: 14, color: CupertinoColors.systemRed), + const Icon( + CupertinoIcons.wifi_exclamationmark, + size: 14, + color: CupertinoColors.systemRed, + ), const SizedBox(width: AppSpacing.xs), Text( '网络连接异常,部分内容可能无法加载', - style: AppTypography.caption1.copyWith(color: CupertinoColors.systemRed), + style: AppTypography.caption1.copyWith( + color: CupertinoColors.systemRed, + ), ), ], ), @@ -223,14 +264,25 @@ class _RssReaderPageState extends ConsumerState { // ── 订阅源首页(聚合视图) ── - Widget _buildSubscriptionHome(AppThemeExtension ext, RssState state, RssNotifier notifier) { + Widget _buildSubscriptionHome( + AppThemeExtension ext, + RssState state, + RssNotifier notifier, + ) { final filteredSubs = _getFilteredSubscriptions(state); return CustomScrollView( physics: const AlwaysScrollableScrollPhysics(), slivers: [ SliverPadding( - padding: const EdgeInsets.fromLTRB(AppSpacing.md, AppSpacing.md, AppSpacing.md, 0), - sliver: SliverToBoxAdapter(child: _buildCategoryFilter(ext, state, notifier)), + padding: const EdgeInsets.fromLTRB( + AppSpacing.md, + AppSpacing.md, + AppSpacing.md, + 0, + ), + sliver: SliverToBoxAdapter( + child: _buildCategoryFilter(ext, state, notifier), + ), ), SliverPadding( padding: const EdgeInsets.all(AppSpacing.md), @@ -239,9 +291,18 @@ class _RssReaderPageState extends ConsumerState { children: [ const Text('📡', style: TextStyle(fontSize: 20)), const SizedBox(width: AppSpacing.sm), - Text('我的订阅', style: AppTypography.headline.copyWith(color: ext.textPrimary, fontWeight: FontWeight.w600)), + Text( + '我的订阅', + style: AppTypography.headline.copyWith( + color: ext.textPrimary, + fontWeight: FontWeight.w600, + ), + ), const SizedBox(width: AppSpacing.sm), - Text('${filteredSubs.length}', style: AppTypography.caption1.copyWith(color: ext.textHint)), + Text( + '${filteredSubs.length}', + style: AppTypography.caption1.copyWith(color: ext.textHint), + ), ], ), ), @@ -270,30 +331,56 @@ class _RssReaderPageState extends ConsumerState { ); } - Widget _buildCategoryFilter(AppThemeExtension ext, RssState state, RssNotifier notifier) { + Widget _buildCategoryFilter( + AppThemeExtension ext, + RssState state, + RssNotifier notifier, + ) { return SizedBox( height: 36, child: ListView( scrollDirection: Axis.horizontal, children: [ - _buildCategoryChip(ext, label: '全部', isSelected: state.selectedCategory == null, onTap: () => notifier.setCategory(null)), + _buildCategoryChip( + ext, + label: '全部', + isSelected: state.selectedCategory == null, + onTap: () => notifier.setCategory(null), + ), const SizedBox(width: AppSpacing.xs), - ...RssCategory.values.map((cat) => Padding( - padding: const EdgeInsets.only(right: AppSpacing.xs), - child: _buildCategoryChip(ext, label: cat.label, isSelected: state.selectedCategory == cat, onTap: () => notifier.setCategory(cat)), - )), + ...RssCategory.values.map( + (cat) => Padding( + padding: const EdgeInsets.only(right: AppSpacing.xs), + child: _buildCategoryChip( + ext, + label: cat.label, + isSelected: state.selectedCategory == cat, + onTap: () => notifier.setCategory(cat), + ), + ), + ), ], ), ); } - Widget _buildCategoryChip(AppThemeExtension ext, {required String label, required bool isSelected, required VoidCallback onTap}) { + Widget _buildCategoryChip( + AppThemeExtension ext, { + required String label, + required bool isSelected, + required VoidCallback onTap, + }) { return GestureDetector( onTap: onTap, child: Container( - padding: const EdgeInsets.symmetric(horizontal: AppSpacing.sm + 2, vertical: AppSpacing.xs + 1), + padding: const EdgeInsets.symmetric( + horizontal: AppSpacing.sm + 2, + vertical: AppSpacing.xs + 1, + ), decoration: BoxDecoration( - color: isSelected ? ext.accent.withValues(alpha: 0.12) : ext.bgSecondary, + color: isSelected + ? ext.accent.withValues(alpha: 0.12) + : ext.bgSecondary, borderRadius: AppRadius.mdBorder, border: isSelected ? Border.all(color: ext.accent) : null, ), @@ -315,9 +402,15 @@ class _RssReaderPageState extends ConsumerState { children: [ const Text('📡', style: TextStyle(fontSize: 56)), const SizedBox(height: AppSpacing.md), - Text('暂无订阅源', style: AppTypography.headline.copyWith(color: ext.textSecondary)), + Text( + '暂无订阅源', + style: AppTypography.headline.copyWith(color: ext.textSecondary), + ), const SizedBox(height: AppSpacing.sm), - Text('点击右上角 + 添加RSS订阅源', style: AppTypography.subhead.copyWith(color: ext.textHint)), + Text( + '点击右上角 + 添加RSS订阅源', + style: AppTypography.subhead.copyWith(color: ext.textHint), + ), const SizedBox(height: AppSpacing.lg), SizedBox( width: 200, @@ -325,7 +418,13 @@ class _RssReaderPageState extends ConsumerState { color: ext.accent, borderRadius: AppRadius.lgBorder, onPressed: _showAddSubscriptionSheet, - child: Text('➕ 添加订阅', style: AppTypography.subhead.copyWith(color: ext.textOnAccent, fontWeight: FontWeight.w600)), + child: Text( + '➕ 添加订阅', + style: AppTypography.subhead.copyWith( + color: ext.textOnAccent, + fontWeight: FontWeight.w600, + ), + ), ), ), ], @@ -343,7 +442,11 @@ class _RssReaderPageState extends ConsumerState { // ── 文章列表 ── - Widget _buildArticleList(AppThemeExtension ext, RssState state, RssNotifier notifier) { + Widget _buildArticleList( + AppThemeExtension ext, + RssState state, + RssNotifier notifier, + ) { return Column( children: [ // 搜索栏 @@ -351,16 +454,16 @@ class _RssReaderPageState extends ConsumerState { // 文章内容区域 Expanded( child: state.isLoading - ? _buildShimmerList() - : state.errorMessage != null + ? _buildShimmerList() + : state.errorMessage != null ? _buildErrorState(ext, notifier) : state.searchQuery.isNotEmpty - ? _buildSearchResults(ext, state, notifier) - : state.feedItems.isEmpty - ? _buildEmptyArticleState(ext, notifier) - : state.isCardMode - ? _buildCardModeView(ext, state, notifier) - : _buildArticleListView(ext, state, notifier), + ? _buildSearchResults(ext, state, notifier) + : state.feedItems.isEmpty + ? _buildEmptyArticleState(ext, notifier) + : state.isCardMode + ? _buildCardModeView(ext, state, notifier) + : _buildArticleListView(ext, state, notifier), ), ], ); @@ -371,7 +474,11 @@ class _RssReaderPageState extends ConsumerState { return ListView.builder( itemCount: 5, itemBuilder: (_, __) => Padding( - padding: const EdgeInsets.only(bottom: AppSpacing.sm, left: AppSpacing.md, right: AppSpacing.md), + padding: const EdgeInsets.only( + bottom: AppSpacing.sm, + left: AppSpacing.md, + right: AppSpacing.md, + ), child: ShimmerPlaceholder.card(), ), ); @@ -385,7 +492,10 @@ class _RssReaderPageState extends ConsumerState { children: [ const Text('📭', style: TextStyle(fontSize: 48)), const SizedBox(height: AppSpacing.md), - Text('暂无文章', style: AppTypography.subhead.copyWith(color: ext.textHint)), + Text( + '暂无文章', + style: AppTypography.subhead.copyWith(color: ext.textHint), + ), const SizedBox(height: AppSpacing.md), CupertinoButton( onPressed: () => notifier.loadFeed(refresh: true), @@ -397,7 +507,11 @@ class _RssReaderPageState extends ConsumerState { } /// 文章列表视图(非卡片模式) - Widget _buildArticleListView(AppThemeExtension ext, RssState state, RssNotifier notifier) { + Widget _buildArticleListView( + AppThemeExtension ext, + RssState state, + RssNotifier notifier, + ) { return RefreshIndicator( color: ext.accent, onRefresh: () => notifier.loadFeed(refresh: true), @@ -405,11 +519,18 @@ class _RssReaderPageState extends ConsumerState { physics: const AlwaysScrollableScrollPhysics(), slivers: [ SliverPadding( - padding: const EdgeInsets.only(left: AppSpacing.md, right: AppSpacing.md, top: AppSpacing.sm), + padding: const EdgeInsets.only( + left: AppSpacing.md, + right: AppSpacing.md, + top: AppSpacing.sm, + ), sliver: SliverList( delegate: SliverChildBuilderDelegate((context, index) { final item = state.feedItems[index]; - return RssArticleCard(item: item, onTap: () => notifier.openDetail(item)); + return RssArticleCard( + item: item, + onTap: () => notifier.openDetail(item), + ); }, childCount: state.feedItems.length), ), ), @@ -441,13 +562,23 @@ class _RssReaderPageState extends ConsumerState { children: [ const Text('😔', style: TextStyle(fontSize: 48)), const SizedBox(height: AppSpacing.md), - Text(state.errorMessage ?? '加载失败', style: AppTypography.subhead.copyWith(color: ext.textHint), textAlign: TextAlign.center), + Text( + state.errorMessage ?? '加载失败', + style: AppTypography.subhead.copyWith(color: ext.textHint), + textAlign: TextAlign.center, + ), const SizedBox(height: AppSpacing.md), CupertinoButton( color: ext.accent, borderRadius: AppRadius.lgBorder, onPressed: () => notifier.loadFeed(refresh: true), - child: Text('🔄 重试', style: AppTypography.subhead.copyWith(color: ext.textOnAccent, fontWeight: FontWeight.w600)), + child: Text( + '🔄 重试', + style: AppTypography.subhead.copyWith( + color: ext.textOnAccent, + fontWeight: FontWeight.w600, + ), + ), ), ], ), @@ -457,15 +588,22 @@ class _RssReaderPageState extends ConsumerState { // ── 文章详情 ── - Widget _buildArticleDetail(AppThemeExtension ext, RssState state, RssNotifier notifier) { + Widget _buildArticleDetail( + AppThemeExtension ext, + RssState state, + RssNotifier notifier, + ) { final item = state.selectedItem; if (item == null) return const SizedBox.shrink(); - final isReadingMode = state.fullTextResult != null && state.fullTextResult!.success; + final isReadingMode = + state.fullTextResult != null && state.fullTextResult!.success; final isBookmarked = state.bookmarkedUids.contains(item.uid); // 获取已保存的阅读进度 final savedProgress = notifier.getReadingProgress(item.uid); - final scrollController = ScrollController(initialScrollOffset: savedProgress); + final scrollController = ScrollController( + initialScrollOffset: savedProgress, + ); return NotificationListener( onNotification: (notification) { @@ -492,11 +630,18 @@ class _RssReaderPageState extends ConsumerState { children: [ // 标题 Text( - isReadingMode ? (state.fullTextResult!.title ?? item.title) : item.title, - style: AppTypography.title2.copyWith(color: ext.textPrimary, fontWeight: FontWeight.bold), + isReadingMode + ? (state.fullTextResult!.title ?? item.title) + : item.title, + style: AppTypography.title2.copyWith( + color: ext.textPrimary, + fontWeight: FontWeight.bold, + ), ), // 元信息 - if (item.sourceTitle != null || item.author != null || item.pubDate != null) ...[ + if (item.sourceTitle != null || + item.author != null || + item.pubDate != null) ...[ const SizedBox(height: AppSpacing.sm), _buildMetaRow(ext, item), ], @@ -506,17 +651,37 @@ class _RssReaderPageState extends ConsumerState { const Center(child: CupertinoActivityIndicator()), const SizedBox(height: AppSpacing.md), ] else if (isReadingMode) ...[ - Text(state.fullTextResult!.content ?? '', style: AppTypography.body.copyWith(color: ext.textPrimary, height: 1.8)), + Text( + state.fullTextResult!.content ?? '', + style: AppTypography.body.copyWith( + color: ext.textPrimary, + height: 1.8, + ), + ), if (state.fullTextResult!.images.isNotEmpty) ...[ const SizedBox(height: AppSpacing.lg), _buildImageGallery(ext, state.fullTextResult!.images), ], - ] else if (item.description != null && item.description!.isNotEmpty) ...[ - Text(HtmlUtils.stripTags(item.description!), style: AppTypography.body.copyWith(color: ext.textSecondary, height: 1.7)), + ] else if (item.description != null && + item.description!.isNotEmpty) ...[ + Text( + HtmlUtils.stripTags(item.description!), + style: AppTypography.body.copyWith( + color: ext.textSecondary, + height: 1.7, + ), + ), ], const SizedBox(height: AppSpacing.lg), // 操作按钮 - if (item.link != null) _buildActionButtons(ext, notifier, item, isReadingMode, isBookmarked), + if (item.link != null) + _buildActionButtons( + ext, + notifier, + item, + isReadingMode, + isBookmarked, + ), ], ), ), @@ -534,10 +699,16 @@ class _RssReaderPageState extends ConsumerState { child: ClipRRect( borderRadius: AppRadius.lgBorder, child: CachedNetworkImage( - imageUrl: imageUrl, width: double.infinity, height: 200, fit: BoxFit.cover, + imageUrl: imageUrl, + width: double.infinity, + height: 200, + fit: BoxFit.cover, placeholder: (_, __) => Container( height: 200, - decoration: BoxDecoration(color: ext.bgSecondary, borderRadius: AppRadius.lgBorder), + decoration: BoxDecoration( + color: ext.bgSecondary, + borderRadius: AppRadius.lgBorder, + ), child: const Center(child: CupertinoActivityIndicator()), ), errorWidget: (_, __, ___) => const SizedBox.shrink(), @@ -547,7 +718,13 @@ class _RssReaderPageState extends ConsumerState { } /// 操作按钮区域(查看原文 + 收藏 + 阅读模式) - Widget _buildActionButtons(AppThemeExtension ext, RssNotifier notifier, RssFeedItem item, bool isReadingMode, bool isBookmarked) { + Widget _buildActionButtons( + AppThemeExtension ext, + RssNotifier notifier, + RssFeedItem item, + bool isReadingMode, + bool isBookmarked, + ) { return Column( children: [ Row( @@ -561,9 +738,19 @@ class _RssReaderPageState extends ConsumerState { child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(CupertinoIcons.doc_text_search, size: 16, color: ext.textOnAccent), + Icon( + CupertinoIcons.doc_text_search, + size: 16, + color: ext.textOnAccent, + ), const SizedBox(width: AppSpacing.xs), - Text('查看原文', style: AppTypography.subhead.copyWith(color: ext.textOnAccent, fontWeight: FontWeight.w600)), + Text( + '查看原文', + style: AppTypography.subhead.copyWith( + color: ext.textOnAccent, + fontWeight: FontWeight.w600, + ), + ), ], ), ), @@ -571,7 +758,10 @@ class _RssReaderPageState extends ConsumerState { const SizedBox(width: AppSpacing.sm), // 收藏按钮 CupertinoButton( - padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md, vertical: AppSpacing.sm), + padding: const EdgeInsets.symmetric( + horizontal: AppSpacing.md, + vertical: AppSpacing.sm, + ), color: ext.accent.withValues(alpha: 0.12), borderRadius: AppRadius.lgBorder, onPressed: () { @@ -581,9 +771,21 @@ class _RssReaderPageState extends ConsumerState { child: Row( mainAxisSize: MainAxisSize.min, children: [ - Icon(isBookmarked ? CupertinoIcons.bookmark_fill : CupertinoIcons.bookmark, size: 16, color: ext.accent), + Icon( + isBookmarked + ? CupertinoIcons.bookmark_fill + : CupertinoIcons.bookmark, + size: 16, + color: ext.accent, + ), const SizedBox(width: AppSpacing.xs), - Text(isBookmarked ? '已收藏' : '收藏', style: AppTypography.subhead.copyWith(color: ext.accent, fontWeight: FontWeight.w600)), + Text( + isBookmarked ? '已收藏' : '收藏', + style: AppTypography.subhead.copyWith( + color: ext.accent, + fontWeight: FontWeight.w600, + ), + ), ], ), ), @@ -595,7 +797,10 @@ class _RssReaderPageState extends ConsumerState { SizedBox( width: double.infinity, child: CupertinoButton( - padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md, vertical: AppSpacing.sm), + padding: const EdgeInsets.symmetric( + horizontal: AppSpacing.md, + vertical: AppSpacing.sm, + ), color: ext.accent.withValues(alpha: 0.12), borderRadius: AppRadius.lgBorder, onPressed: notifier.loadFullText, @@ -604,7 +809,13 @@ class _RssReaderPageState extends ConsumerState { children: [ Icon(CupertinoIcons.doc_text, size: 16, color: ext.accent), const SizedBox(width: AppSpacing.xs), - Text('📖 阅读模式', style: AppTypography.subhead.copyWith(color: ext.accent, fontWeight: FontWeight.w600)), + Text( + '📖 阅读模式', + style: AppTypography.subhead.copyWith( + color: ext.accent, + fontWeight: FontWeight.w600, + ), + ), ], ), ), @@ -622,10 +833,16 @@ class _RssReaderPageState extends ConsumerState { title: const Text('查看原文'), content: const Text('即将跳转到外部浏览器打开原文链接,是否继续?'), actions: [ - CupertinoDialogAction(onPressed: () => Navigator.of(ctx).pop(), child: const Text('取消')), + CupertinoDialogAction( + onPressed: () => Navigator.of(ctx).pop(), + child: const Text('取消'), + ), CupertinoDialogAction( isDefaultAction: true, - onPressed: () { Navigator.of(ctx).pop(); _launchUrl(url); }, + onPressed: () { + Navigator.of(ctx).pop(); + _launchUrl(url); + }, child: const Text('继续'), ), ], @@ -638,26 +855,47 @@ class _RssReaderPageState extends ConsumerState { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('📎 文中图片', style: AppTypography.subhead.copyWith(color: ext.textSecondary, fontWeight: FontWeight.w600)), + Text( + '📎 文中图片', + style: AppTypography.subhead.copyWith( + color: ext.textSecondary, + fontWeight: FontWeight.w600, + ), + ), const SizedBox(height: AppSpacing.sm), Wrap( spacing: AppSpacing.sm, runSpacing: AppSpacing.sm, - children: images.map((url) => GestureDetector( - onTap: () => Navigator.of(context).push(CupertinoPageRoute(builder: (_) => FullScreenPhotoView(imageUrl: url))), - child: ClipRRect( - borderRadius: AppRadius.mdBorder, - child: CachedNetworkImage( - imageUrl: url, width: 100, height: 100, fit: BoxFit.cover, - placeholder: (_, __) => Container( - width: 100, height: 100, - decoration: BoxDecoration(color: ext.bgSecondary, borderRadius: AppRadius.mdBorder), - child: const CupertinoActivityIndicator(radius: 8), + children: images + .map( + (url) => GestureDetector( + onTap: () => Navigator.of(context).push( + CupertinoPageRoute( + builder: (_) => FullScreenPhotoView(imageUrl: url), + ), + ), + child: ClipRRect( + borderRadius: AppRadius.mdBorder, + child: CachedNetworkImage( + imageUrl: url, + width: 100, + height: 100, + fit: BoxFit.cover, + placeholder: (_, __) => Container( + width: 100, + height: 100, + decoration: BoxDecoration( + color: ext.bgSecondary, + borderRadius: AppRadius.mdBorder, + ), + child: const CupertinoActivityIndicator(radius: 8), + ), + errorWidget: (_, __, ___) => const SizedBox.shrink(), + ), + ), ), - errorWidget: (_, __, ___) => const SizedBox.shrink(), - ), - ), - )).toList(), + ) + .toList(), ), ], ); @@ -666,26 +904,52 @@ class _RssReaderPageState extends ConsumerState { /// 文章元信息行 Widget _buildMetaRow(AppThemeExtension ext, RssFeedItem item) { return Container( - padding: const EdgeInsets.symmetric(horizontal: AppSpacing.sm, vertical: AppSpacing.xs), - decoration: BoxDecoration(color: ext.bgSecondary, borderRadius: AppRadius.smBorder), + padding: const EdgeInsets.symmetric( + horizontal: AppSpacing.sm, + vertical: AppSpacing.xs, + ), + decoration: BoxDecoration( + color: ext.bgSecondary, + borderRadius: AppRadius.smBorder, + ), child: Row( children: [ if (item.sourceTitle != null) ...[ - Icon(CupertinoIcons.antenna_radiowaves_left_right, size: 12, color: ext.accent), + Icon( + CupertinoIcons.antenna_radiowaves_left_right, + size: 12, + color: ext.accent, + ), const SizedBox(width: 3), - Text(item.sourceTitle!, style: AppTypography.caption2.copyWith(color: ext.accent, fontWeight: FontWeight.w500)), + Text( + item.sourceTitle!, + style: AppTypography.caption2.copyWith( + color: ext.accent, + fontWeight: FontWeight.w500, + ), + ), const SizedBox(width: AppSpacing.sm), ], if (item.author != null) ...[ Icon(CupertinoIcons.person, size: 12, color: ext.textHint), const SizedBox(width: 3), - Flexible(child: Text(item.author!, style: AppTypography.caption2.copyWith(color: ext.textHint), maxLines: 1, overflow: TextOverflow.ellipsis)), + Flexible( + child: Text( + item.author!, + style: AppTypography.caption2.copyWith(color: ext.textHint), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), const SizedBox(width: AppSpacing.sm), ], if (item.pubDate != null) ...[ Icon(CupertinoIcons.time, size: 12, color: ext.textHint), const SizedBox(width: 3), - Text(item.pubDate!.timeAgo, style: AppTypography.caption2.copyWith(color: ext.textHint)), + Text( + item.pubDate!.timeAgo, + style: AppTypography.caption2.copyWith(color: ext.textHint), + ), ], ], ), @@ -717,20 +981,29 @@ class _RssReaderPageState extends ConsumerState { } else { text = '${item.title}\n\n${HtmlUtils.stripTags(item.description ?? '')}'; } - if (text.trim().isEmpty) { _showToast('暂无内容可朗读'); return; } + if (text.trim().isEmpty) { + _showToast('暂无内容可朗读'); + return; + } TtsPlayerSheet.show(context, text: text); } // ── 删除订阅 ── - Future _deleteSubscription(RssSubscription sub, RssNotifier notifier) async { + Future _deleteSubscription( + RssSubscription sub, + RssNotifier notifier, + ) async { showCupertinoDialog( context: context, builder: (ctx) => CupertinoAlertDialog( title: const Text('删除订阅源'), content: Text('确定要删除「${sub.title}」吗?'), actions: [ - CupertinoDialogAction(onPressed: () => Navigator.of(ctx).pop(), child: const Text('取消')), + CupertinoDialogAction( + onPressed: () => Navigator.of(ctx).pop(), + child: const Text('取消'), + ), CupertinoDialogAction( isDestructiveAction: true, onPressed: () async { @@ -752,7 +1025,8 @@ class _RssReaderPageState extends ConsumerState { return GestureDetector( onTap: () => _showMoreActions(ext, notifier), child: Container( - width: 32, height: 32, + width: 32, + height: 32, decoration: BoxDecoration( color: ext.bgSecondary, borderRadius: AppRadius.mdBorder, @@ -769,11 +1043,17 @@ class _RssReaderPageState extends ConsumerState { builder: (ctx) => CupertinoActionSheet( actions: [ CupertinoActionSheetAction( - onPressed: () { Navigator.of(ctx).pop(); _exportOpml(notifier); }, + onPressed: () { + Navigator.of(ctx).pop(); + _exportOpml(notifier); + }, child: Text('📤 导出OPML', style: TextStyle(color: ext.accent)), ), CupertinoActionSheetAction( - onPressed: () { Navigator.of(ctx).pop(); _importOpml(notifier); }, + onPressed: () { + Navigator.of(ctx).pop(); + _importOpml(notifier); + }, child: Text('📥 导入OPML', style: TextStyle(color: ext.accent)), ), ], @@ -792,10 +1072,9 @@ class _RssReaderPageState extends ConsumerState { final dir = await getTemporaryDirectory(); final file = File('${dir.path}/rss_subscriptions.opml'); await file.writeAsString(opml); - await SharePlus.instance.share(ShareParams( - text: 'RSS订阅源导出', - files: [XFile(file.path)], - )); + await SharePlus.instance.share( + ShareParams(text: 'RSS订阅源导出', files: [XFile(file.path)]), + ); } catch (e) { Log.e('RssReader', 'OPML导出失败: $e'); _showToast('❌ 导出失败'); @@ -815,8 +1094,12 @@ class _RssReaderPageState extends ConsumerState { if (file.path != null) { opmlXml = await File(file.path!).readAsString(); } else { - // 降级:使用readAsBytes读取 - final bytes = await file.readAsBytes(); + // 降级:使用 bytes 属性读取(file_picker 11.x API) + final bytes = file.bytes; + if (bytes == null || bytes.isEmpty) { + _showToast('❌ 无法读取文件数据'); + return; + } opmlXml = utf8.decode(bytes); } final count = await notifier.importOpml(opmlXml); @@ -834,7 +1117,11 @@ class _RssReaderPageState extends ConsumerState { // ── 文章搜索 ── /// 搜索栏(使用RssSearchBar组件) - Widget _buildSearchBar(AppThemeExtension ext, RssState state, RssNotifier notifier) { + Widget _buildSearchBar( + AppThemeExtension ext, + RssState state, + RssNotifier notifier, + ) { return RssSearchBar( searchQuery: state.searchQuery, onChanged: (query) => notifier.searchArticles(query), @@ -843,7 +1130,11 @@ class _RssReaderPageState extends ConsumerState { } /// 搜索结果列表(使用RssSearchResultsList组件) - Widget _buildSearchResults(AppThemeExtension ext, RssState state, RssNotifier notifier) { + Widget _buildSearchResults( + AppThemeExtension ext, + RssState state, + RssNotifier notifier, + ) { return RssSearchResultsList( searchResults: state.searchResults, isSearching: state.isSearching, @@ -860,7 +1151,11 @@ class _RssReaderPageState extends ConsumerState { // ── 卡片式阅读模式 ── /// 卡片阅读模式视图(使用RssCardModeView组件) - Widget _buildCardModeView(AppThemeExtension ext, RssState state, RssNotifier notifier) { + Widget _buildCardModeView( + AppThemeExtension ext, + RssState state, + RssNotifier notifier, + ) { return RssCardModeView( feedItems: state.feedItems, bookmarkedUids: state.bookmarkedUids, @@ -880,7 +1175,8 @@ class _RssReaderPageState extends ConsumerState { Future _launchUrl(String url) async { try { final uri = Uri.parse(url); - if (await canLaunchUrl(uri)) await launchUrl(uri, mode: LaunchMode.externalApplication); + if (await canLaunchUrl(uri)) + await launchUrl(uri, mode: LaunchMode.externalApplication); } catch (e) { Log.e('RssReader', '打开链接失败: $e'); } @@ -890,7 +1186,11 @@ class _RssReaderPageState extends ConsumerState { if (!mounted) return; HapticFeedback.lightImpact(); ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(message), duration: const Duration(seconds: 2), behavior: SnackBarBehavior.floating), + SnackBar( + content: Text(message), + duration: const Duration(seconds: 2), + behavior: SnackBarBehavior.floating, + ), ); } } diff --git a/lib/features/settings/presentation/font_management_notifier.dart b/lib/features/settings/presentation/font_management_notifier.dart index 93f3998d..05233479 100644 --- a/lib/features/settings/presentation/font_management_notifier.dart +++ b/lib/features/settings/presentation/font_management_notifier.dart @@ -662,9 +662,7 @@ class FontManagementNotifier extends Notifier return; } try { - final result = await FilePicker.pickFiles( - dialogTitle: '选择ZIP字体包', - ); + final result = await FilePicker.pickFiles(dialogTitle: '选择ZIP字体包'); if (result == null || result.files.isEmpty) return; final platformFile = result.files.first; @@ -684,7 +682,8 @@ class FontManagementNotifier extends Notifier zipBytes = await zipFile.readAsBytes(); } } - + // 降级:使用readAsBytes读取 + // final bytes = await file.readAsBytes(); // 路径不可用时,使用 bytes 属性读取(file_picker 11.x API) if (zipBytes == null) { final rawBytes = platformFile.bytes;