From 9cf832b318f99f1e02bc2a7e5789fdbd685e04f7 Mon Sep 17 00:00:00 2001 From: Zhou Weidong <502333560@qq.com> Date: Thu, 22 Oct 2026 17:58:43 +0800 Subject: [PATCH] =?UTF-8?q?refactor(navigation):=20=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E5=85=A8=E5=B1=80=E4=B8=A4=E4=BE=A7=E6=82=AC=E6=B5=AE=E5=85=83?= =?UTF-8?q?=E7=B4=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/plans/PROGRESS.md | 31 +++++ lib/core/theme/app_dimens.dart | 5 - lib/pages/mine/mine_view.dart | 85 +++---------- lib/pages/mine/widgets/mine_mini_player.dart | 124 ------------------- test/home/home_grid_layout_test.dart | 2 + test/home/home_nav_ui_test.dart | 41 ++++++ test/home/k28_a_navigation_test.dart | 9 ++ test/mine/mine_preview_layout_test.dart | 23 +--- 8 files changed, 103 insertions(+), 217 deletions(-) delete mode 100644 lib/pages/mine/widgets/mine_mini_player.dart diff --git a/docs/plans/PROGRESS.md b/docs/plans/PROGRESS.md index e62f42d..0a3327d 100644 --- a/docs/plans/PROGRESS.md +++ b/docs/plans/PROGRESS.md @@ -1039,3 +1039,34 @@ $ flutter test 解读:用户区、UID、登录/编辑、VIP、7 项菜单顺序与跳转、旧入口可达性、 迷你播放器显隐/关闭/开播及无数据不渲染均有测试覆盖。`mine_view.dart` 与新增子组件均低于 500 行;`pubspec.lock` 未提交,未操作真机。 + +## 2026-10-22 全局移除两侧悬浮元素 + +- 分支:`feature/remove-floating-overlays` +- 基线:`develop` / `origin/develop` = `5cead7c` + +### 改动 + +- 保留首页顶部礼物入口的 `home-wallet-badge` 红色余额角标。 +- 删除「我的」页 `MineMiniPlayer` 文件、渲染分支及最近观看开播逻辑。 +- 删除仅为迷你播放器新增的 `mineMini*` 尺寸 token;没有新增专属 l10n key。 +- 首页、推荐、剧单、全部短剧和追剧页均不渲染两侧游戏/活动悬浮球、 + 悬浮关闭按钮或迷你播放器。 +- 卡片角标、播放量、标题、标签和顶部钱包红标保持不变。 + +### 门禁命令与原始输出 + +```text +$ flutter analyze +Analyzing reelshort-home-floating... +No issues found! (ran in 2.5s) +``` + +```text +$ flutter test +01:32 +647: All tests passed! +``` + +解读:登录且钱包快照存在时钱包红标仍显示;首页四个 tab、推荐三个子 tab、 +追剧页和我的页均断言不存在两侧悬浮组件。`pubspec.lock` 未提交, +未操作真机。 diff --git a/lib/core/theme/app_dimens.dart b/lib/core/theme/app_dimens.dart index 7706f55..c752040 100644 --- a/lib/core/theme/app_dimens.dart +++ b/lib/core/theme/app_dimens.dart @@ -97,9 +97,4 @@ abstract final class AppDimens { static const double mineUserName = 20; static const double mineUid = 13; static const double mineMenuIcon = 22; - static const double mineMiniWidth = 96; - static const double mineMiniHeight = 132; - static const double mineMiniRadius = 8; - static const double mineMiniTitle = 10; - static const double mineMiniClose = 22; } diff --git a/lib/pages/mine/mine_view.dart b/lib/pages/mine/mine_view.dart index 3f77213..fea424f 100644 --- a/lib/pages/mine/mine_view.dart +++ b/lib/pages/mine/mine_view.dart @@ -5,11 +5,8 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import '../../core/data/models/drama.dart'; -import '../../core/data/models/watch_progress.dart'; import '../../core/data/repositories/watch_history_repository.dart'; import '../../core/data/services/auth_controller.dart'; -import '../../core/data/services/catalog_controller.dart'; import '../../core/data/services/locale_controller.dart'; import '../../core/data/services/wallet_controller.dart'; import '../../core/theme/app_colors.dart'; @@ -23,7 +20,6 @@ import '../downloads/downloads_page.dart'; import '../account/account_profile_page.dart'; import '../filter/filter_page.dart'; import '../notices/notices_page.dart'; -import '../player/open_player.dart'; import '../settings/settings_page.dart'; import '../store/vip_page.dart'; import '../wallet/wallet_page.dart'; @@ -33,7 +29,6 @@ import 'mine_logic.dart'; import 'mine_types.dart'; import 'widgets/mine_header.dart'; import 'widgets/mine_menu.dart'; -import 'widgets/mine_mini_player.dart'; /// K28-B 我的页。 class MyPage extends StatefulWidget { @@ -52,7 +47,6 @@ class MyPage extends StatefulWidget { class _MyPageState extends State { late final MineLogic logic; - bool _miniDismissed = false; bool get embedded => widget.callbacks.embedded; @@ -133,44 +127,23 @@ class _MyPageState extends State { Widget _body(BuildContext context, AppLocalizations l10n) { final locale = context.watch(); - final recent = _miniRecord; - return Stack( + return ListView( + key: const Key('mine-page-scroll'), + padding: const EdgeInsets.fromLTRB( + AppDimens.pageHorizontal, + 0, + AppDimens.pageHorizontal, + AppDimens.spaceXl, + ), children: [ - Positioned.fill( - child: ListView( - key: const Key('mine-page-scroll'), - padding: EdgeInsets.fromLTRB( - AppDimens.pageHorizontal, - 0, - AppDimens.pageHorizontal, - recent == null ? AppDimens.spaceXl : 156, - ), - children: [ - MineHeader(onLogin: _openLogin, onEdit: _openEditActions), - MineVipBanner( - onOpen: () => _openDestination(MineDestination.vip), - ), - const SizedBox(height: AppDimens.spaceMd), - MineMenuList( - onOpen: _openDestination, - onLanguage: _openLanguage, - languageLabel: _languageLabel(l10n, locale.locale), - ), - ], - ), + MineHeader(onLogin: _openLogin, onEdit: _openEditActions), + MineVipBanner(onOpen: () => _openDestination(MineDestination.vip)), + const SizedBox(height: AppDimens.spaceMd), + MineMenuList( + onOpen: _openDestination, + onLanguage: _openLanguage, + languageLabel: _languageLabel(l10n, locale.locale), ), - if (recent != null) - Positioned( - left: AppDimens.pageHorizontal, - bottom: AppDimens.spaceMd, - child: MineMiniPlayer( - title: _miniTitle(l10n, recent), - coverUrl: recent.dramaCover?.trim() ?? '', - episodeLabel: l10n.episodeLabel(recent.episodeNumber ?? 0), - onTap: () => unawaited(_openRecent(recent)), - onClose: () => setState(() => _miniDismissed = true), - ), - ), ], ); } @@ -300,34 +273,6 @@ class _MyPageState extends State { await context.read().select(selected); } - WatchProgress? get _miniRecord { - if (_miniDismissed || logic.recent.isEmpty) return null; - final recent = logic.recent.first; - return recent.episodeId.isEmpty ? null : recent; - } - - String _miniTitle(AppLocalizations l10n, WatchProgress recent) { - final title = recent.dramaTitle?.trim() ?? ''; - return title.isEmpty ? l10n.episodeLabel(recent.episodeNumber ?? 0) : title; - } - - Future _openRecent(WatchProgress recent) async { - final catalog = context.read(); - final seed = Drama( - id: recent.dramaId, - title: recent.dramaTitle?.trim() ?? '', - synopsis: '', - posterUrl: recent.dramaCover?.trim() ?? '', - ); - try { - final detail = await catalog.requireDetail(seed); - if (!mounted) return; - await playDramaFromCatalog(context, detail, episodeId: recent.episodeId); - } catch (_) { - // 最近记录失效时不投射错误页,等待下一次记录刷新。 - } - } - Widget _bottomNav() { return AppRootBottomNav( selected: AppRootTab.mine, diff --git a/lib/pages/mine/widgets/mine_mini_player.dart b/lib/pages/mine/widgets/mine_mini_player.dart deleted file mode 100644 index 5886061..0000000 --- a/lib/pages/mine/widgets/mine_mini_player.dart +++ /dev/null @@ -1,124 +0,0 @@ -// Author: fruit.com.cn - -import 'package:flutter/material.dart'; - -import '../../../core/theme/app_colors.dart'; -import '../../../core/theme/app_dimens.dart'; -import '../../../core/widgets/remote_image.dart'; - -const double _infoHeight = 42; - -/// 「我的」页左下角最近观看迷你播放器。 -class MineMiniPlayer extends StatelessWidget { - const MineMiniPlayer({ - super.key, - required this.title, - required this.coverUrl, - required this.episodeLabel, - required this.onTap, - required this.onClose, - }); - - final String title; - final String coverUrl; - final String episodeLabel; - final VoidCallback onTap; - final VoidCallback onClose; - - @override - Widget build(BuildContext context) { - return SizedBox( - key: const Key('mine-mini-player'), - width: AppDimens.mineMiniWidth, - height: AppDimens.mineMiniHeight, - child: DecoratedBox( - decoration: BoxDecoration( - color: AppColors.mineCard, - borderRadius: BorderRadius.circular(AppDimens.mineMiniRadius), - border: Border.all(color: AppColors.contentBorder), - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(AppDimens.mineMiniRadius), - child: Material( - color: AppColors.transparent, - child: InkWell( - onTap: onTap, - child: Stack( - children: [ - Column( - children: [ - Expanded( - child: RemoteImage( - coverUrl, - fit: BoxFit.cover, - showFallbackIcon: true, - fallbackColor: AppColors.contentSurface, - ), - ), - SizedBox( - height: _infoHeight, - child: ColoredBox( - color: AppColors.mineCard, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 6, - vertical: 4, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - color: AppColors.textPrimary, - fontSize: AppDimens.mineMiniTitle, - fontWeight: FontWeight.w600, - ), - ), - Text( - episodeLabel, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - color: AppColors.textSecondary, - fontSize: AppDimens.mineMiniTitle, - ), - ), - ], - ), - ), - ), - ), - ], - ), - Positioned( - right: 3, - top: 3, - child: IconButton( - key: const Key('mine-mini-close'), - tooltip: MaterialLocalizations.of( - context, - ).closeButtonTooltip, - onPressed: onClose, - style: IconButton.styleFrom( - minimumSize: const Size.square(AppDimens.mineMiniClose), - maximumSize: const Size.square(AppDimens.mineMiniClose), - padding: EdgeInsets.zero, - backgroundColor: AppColors.controlScrim, - foregroundColor: AppColors.textPrimary, - ), - iconSize: 14, - icon: const Icon(Icons.close_rounded), - ), - ), - ], - ), - ), - ), - ), - ), - ); - } -} diff --git a/test/home/home_grid_layout_test.dart b/test/home/home_grid_layout_test.dart index e07119b..e0f4d35 100644 --- a/test/home/home_grid_layout_test.dart +++ b/test/home/home_grid_layout_test.dart @@ -71,6 +71,8 @@ void main() { expect(find.text('Charts'), findsOneWidget); expect(find.text('Costume'), findsOneWidget); expect(find.text('Community'), findsOneWidget); + expect(find.byKey(const Key('mine-mini-player')), findsNothing); + expect(find.byType(FloatingActionButton), findsNothing); }); testWidgets('should_hide_unmatched_content_sections', (tester) async { diff --git a/test/home/home_nav_ui_test.dart b/test/home/home_nav_ui_test.dart index 239a01d..53e7ce4 100644 --- a/test/home/home_nav_ui_test.dart +++ b/test/home/home_nav_ui_test.dart @@ -3,6 +3,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:fruitshorts/app.dart'; +import 'package:fruitshorts/core/data/models/wallet.dart'; +import 'package:fruitshorts/core/data/repositories/commerce_repository.dart'; import 'package:fruitshorts/core/data/repositories/favorite_repository.dart'; import 'package:fruitshorts/core/data/repositories/playback_repository.dart'; import 'package:fruitshorts/core/data/repositories/watch_history_repository.dart'; @@ -46,6 +48,10 @@ void main() { for (final key in _keys) { expect(find.byKey(key), findsOneWidget); } + expect(find.byKey(const Key('home-wallet-entry')), findsOneWidget); + expect(find.byKey(const Key('home-wallet-badge')), findsOneWidget); + expect(find.byKey(const Key('mine-mini-player')), findsNothing); + expect(find.byType(FloatingActionButton), findsNothing); for (final key in _keys.skip(1)) { expect(tester.getSize(find.byKey(key)).height, greaterThanOrEqualTo(48)); } @@ -214,6 +220,7 @@ void main() { await tester.tap(find.byKey(const Key('tab-recommend'))); await tester.pumpAndSettle(); expect(find.byKey(const Key('recommend-page')), findsOneWidget); + _expectNoFloatingOverlays(); await tester.tap(find.byKey(const Key('tab-follow'))); await tester.pumpAndSettle(); @@ -226,6 +233,7 @@ void main() { findsNothing, ); expect(Navigator.canPop(tester.element(favorites)), isFalse); + _expectNoFloatingOverlays(); expect( find.descendant( of: find.byKey(const Key('tab-follow')), @@ -241,6 +249,7 @@ void main() { expect(find.byKey(const Key('mine-wallet')), findsOneWidget); expect(find.byKey(const Key('mine-edit')), findsOneWidget); expect(find.byKey(const Key('mine-library-tab-0')), findsNothing); + _expectNoFloatingOverlays(); await tester.tap(find.byKey(const Key('tab-follow'))); await tester.pumpAndSettle(); @@ -287,6 +296,7 @@ Future _pumpApp(WidgetTester tester, [FakeHaptics? haptics]) async { playbackRepository: FakePlaybackRepository(), commentRepository: FakeCommentRepository(), interactionRepository: FakeInteractionRepository(), + commerceRepository: const _WalletCommerceRepository(), haptics: haptics ?? FakeHaptics(), ), ); @@ -294,3 +304,34 @@ Future _pumpApp(WidgetTester tester, [FakeHaptics? haptics]) async { await tester.tap(find.byKey(const Key('tab-home'))); await tester.pumpAndSettle(); } + +class _WalletCommerceRepository implements CommerceRepository { + const _WalletCommerceRepository(); + + @override + Future fetchWallet() async { + return const WalletSnapshot( + wallet: CoinWallet( + coinBalance: 320, + totalRechargedCoin: 500, + totalConsumedCoin: 180, + totalRefundedCoin: 0, + ), + ); + } + + @override + Future unlock(String episodeId) async { + return const UnlockResult( + unlocked: true, + accessType: 'UNLOCKED', + coinCost: 0, + coinBalance: 320, + ); + } +} + +void _expectNoFloatingOverlays() { + expect(find.byKey(const Key('mine-mini-player')), findsNothing); + expect(find.byType(FloatingActionButton), findsNothing); +} diff --git a/test/home/k28_a_navigation_test.dart b/test/home/k28_a_navigation_test.dart index b88287a..cec7ade 100644 --- a/test/home/k28_a_navigation_test.dart +++ b/test/home/k28_a_navigation_test.dart @@ -82,12 +82,14 @@ void main() { expect(find.byKey(const Key('recommend-tab-playlist')), findsOneWidget); expect(find.byKey(const Key('recommend-tab-all')), findsOneWidget); expect(find.byKey(const Key('recommend-feed-player')), findsOneWidget); + _expectNoFloatingOverlays(); await tester.tap(find.byKey(const Key('recommend-tab-playlist'))); await tester.pumpAndSettle(); expect(find.byKey(const Key('recommend-section-hits')), findsOneWidget); expect(find.byKey(const Key('recommend-section-new')), findsOneWidget); expect(find.byKey(const Key('recommend-section-costume')), findsOneWidget); + _expectNoFloatingOverlays(); }); testWidgets('should_switch_playlist_and_all_dramas_in_recommend', ( @@ -101,6 +103,7 @@ void main() { await tester.pumpAndSettle(); expect(find.byKey(const Key('recommend-section-hits')), findsOneWidget); expect(find.byType(AppBottomNav), findsOneWidget); + _expectNoFloatingOverlays(); await tester.tap(find.byKey(const Key('recommend-tab-all'))); await tester.pumpAndSettle(); @@ -108,6 +111,7 @@ void main() { for (var row = 0; row < 4; row++) { expect(find.byKey(Key('all-filter-row-$row')), findsOneWidget); } + _expectNoFloatingOverlays(); }); testWidgets('should_open_search_from_home_and_recommend', (tester) async { @@ -159,6 +163,11 @@ const _tabKeys = [ Key('tab-mine'), ]; +void _expectNoFloatingOverlays() { + expect(find.byKey(const Key('mine-mini-player')), findsNothing); + expect(find.byType(FloatingActionButton), findsNothing); +} + bool _hasLightStatusBar(WidgetTester tester) => tester .widgetList>( find.byType(AnnotatedRegion), diff --git a/test/mine/mine_preview_layout_test.dart b/test/mine/mine_preview_layout_test.dart index 587a02d..a8396cb 100644 --- a/test/mine/mine_preview_layout_test.dart +++ b/test/mine/mine_preview_layout_test.dart @@ -14,7 +14,6 @@ import 'package:fruitshorts/l10n/app_localizations.dart'; import 'package:fruitshorts/pages/auth/auth_view.dart'; import 'package:fruitshorts/pages/mine/mine_types.dart'; import 'package:fruitshorts/pages/mine/widgets/mine_menu.dart'; -import 'package:fruitshorts/pages/player/player_view.dart'; import 'package:fruitshorts/pages/store/vip_page.dart'; import 'package:shared_preferences_platform_interface/in_memory_shared_preferences_async.dart'; import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart'; @@ -105,7 +104,7 @@ void main() { expect(find.byType(AuthPage), findsOneWidget); }); - testWidgets('should_render_signed_in_user_and_dismiss_mini_player', ( + testWidgets('should_render_signed_in_user_without_floating_mini_player', ( tester, ) async { await _pumpApp( @@ -119,17 +118,8 @@ void main() { expect(find.text('UID: U-100'), findsOneWidget); expect(find.byKey(const Key('mine-edit')), findsOneWidget); expect(find.byKey(const Key('mine-login')), findsNothing); - expect(find.byKey(const Key('mine-mini-player')), findsOneWidget); - expect(find.text('Recent drama'), findsOneWidget); - expect(find.text('Episode 3'), findsOneWidget); - expect( - tester.getSize(find.byKey(const Key('mine-mini-player'))), - const Size(AppDimens.mineMiniWidth, AppDimens.mineMiniHeight), - ); - - await tester.tap(find.byKey(const Key('mine-mini-close'))); - await tester.pump(); expect(find.byKey(const Key('mine-mini-player')), findsNothing); + expect(find.byType(FloatingActionButton), findsNothing); await tester.tap(find.byKey(const Key('mine-edit'))); await tester.pumpAndSettle(); @@ -143,7 +133,7 @@ void main() { expect(tester.takeException(), isNull); }); - testWidgets('should_open_vip_and_recent_player_from_mine_entries', ( + testWidgets('should_open_vip_from_mine_without_floating_player', ( tester, ) async { await _pumpApp( @@ -158,11 +148,8 @@ void main() { expect(find.byType(VipPage), findsOneWidget); await tester.binding.handlePopRoute(); await tester.pumpAndSettle(); - - await tester.tap(find.byKey(const Key('mine-mini-player'))); - await tester.pumpAndSettle(); - final player = tester.widget(find.byType(PlayerPage).last); - expect(player.embedded, isFalse); + expect(find.byKey(const Key('mine-mini-player')), findsNothing); + expect(find.byType(FloatingActionButton), findsNothing); }); testWidgets('should_expose_seven_menu_destinations_in_order', (tester) async {