merge: 统一首页与推荐搜索入口
This commit is contained in:
commit
0319f8a6a8
18 files changed
+325
-107
No files matched your search
@@ -1304,3 +1304,9 @@ $ flutter test
|
|||||||
- 移除视频表面点击识别器,双击后增加 300ms 暂停防护;飘心收敛为 3~4 颗、20~26、±18、90~120dp、600~700ms。
|
- 移除视频表面点击识别器,双击后增加 300ms 暂停防护;飘心收敛为 3~4 颗、20~26、±18、90~120dp、600~700ms。
|
||||||
- 双击窗口延长至 320ms,单击 Timer 使用点击序号校验,双击后保留 350ms 播放切换保护。
|
- 双击窗口延长至 320ms,单击 Timer 使用点击序号校验,双击后保留 350ms 播放切换保护。
|
||||||
- `double_tap_like_test.dart` 覆盖 120/250/300ms 双击与 500ms 单击恢复:2 项测试全部通过。
|
- `double_tap_like_test.dart` 覆盖 120/250/300ms 双击与 500ms 单击恢复:2 项测试全部通过。
|
||||||
|
|
||||||
|
## 2026-10-22 统一二级搜索页
|
||||||
|
|
||||||
|
- 首页与推荐/剧单/全部短剧搜索入口统一 push SearchPage,不再切换推荐 tab 聚焦。
|
||||||
|
- 搜索页提供返回、自动聚焦输入框与取消,复用 `/app/search` 逻辑和首页三列卡片。
|
||||||
|
- 清理旧的 searchRequest/focusSearch/syncSearch 参数;`flutter analyze`:No issues found。
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
"currentSeries": "Current series",
|
"currentSeries": "Current series",
|
||||||
"discoverTab": "Theater",
|
"discoverTab": "Theater",
|
||||||
"searchDiscover": "Search dramas, actors, genres...",
|
"searchDiscover": "Search dramas, actors, genres...",
|
||||||
|
"searchCancel": "Cancel",
|
||||||
"featured": "Featured",
|
"featured": "Featured",
|
||||||
"recommended": "Recommended",
|
"recommended": "Recommended",
|
||||||
"fruitShortsBrand": "FruitShorts",
|
"fruitShortsBrand": "FruitShorts",
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
"currentSeries": "Serial saat ini",
|
"currentSeries": "Serial saat ini",
|
||||||
"discoverTab": "Teater",
|
"discoverTab": "Teater",
|
||||||
"searchDiscover": "Cari drama, aktor, genre...",
|
"searchDiscover": "Cari drama, aktor, genre...",
|
||||||
|
"searchCancel": "Batal",
|
||||||
"featured": "Unggulan",
|
"featured": "Unggulan",
|
||||||
"recommended": "Rekomendasi",
|
"recommended": "Rekomendasi",
|
||||||
"fruitShortsBrand": "FruitShorts",
|
"fruitShortsBrand": "FruitShorts",
|
||||||
|
|||||||
@@ -196,6 +196,12 @@ abstract class AppLocalizations {
|
|||||||
/// **'Search dramas, actors, genres...'**
|
/// **'Search dramas, actors, genres...'**
|
||||||
String get searchDiscover;
|
String get searchDiscover;
|
||||||
|
|
||||||
|
/// No description provided for @searchCancel.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Cancel'**
|
||||||
|
String get searchCancel;
|
||||||
|
|
||||||
/// No description provided for @featured.
|
/// No description provided for @featured.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|||||||
@@ -57,6 +57,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get searchDiscover => 'Search dramas, actors, genres...';
|
String get searchDiscover => 'Search dramas, actors, genres...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get searchCancel => 'Cancel';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get featured => 'Featured';
|
String get featured => 'Featured';
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,9 @@ class AppLocalizationsId extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get searchDiscover => 'Cari drama, aktor, genre...';
|
String get searchDiscover => 'Cari drama, aktor, genre...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get searchCancel => 'Batal';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get featured => 'Unggulan';
|
String get featured => 'Unggulan';
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,9 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get searchDiscover => '搜索短剧、演员、类型…';
|
String get searchDiscover => '搜索短剧、演员、类型…';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get searchCancel => '取消';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get featured => '精选';
|
String get featured => '精选';
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
"currentSeries": "当前短剧",
|
"currentSeries": "当前短剧",
|
||||||
"discoverTab": "剧场",
|
"discoverTab": "剧场",
|
||||||
"searchDiscover": "搜索短剧、演员、类型…",
|
"searchDiscover": "搜索短剧、演员、类型…",
|
||||||
|
"searchCancel": "取消",
|
||||||
"featured": "精选",
|
"featured": "精选",
|
||||||
"recommended": "推荐",
|
"recommended": "推荐",
|
||||||
"fruitShortsBrand": "水果短剧",
|
"fruitShortsBrand": "水果短剧",
|
||||||
|
|||||||
@@ -118,20 +118,6 @@ class DiscoverLogic extends ChangeNotifier {
|
|||||||
searchResults = const [];
|
searchResults = const [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 从播放器发起的搜索请求只在剧场页可见时抢焦点。
|
|
||||||
void syncSearch({
|
|
||||||
required bool active,
|
|
||||||
required int request,
|
|
||||||
required int previousRequest,
|
|
||||||
required bool Function() isActive,
|
|
||||||
}) {
|
|
||||||
if (!active || request == previousRequest) return;
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
if (isActive()) searchFocus.requestFocus();
|
|
||||||
});
|
|
||||||
WidgetsBinding.instance.scheduleFrame();
|
|
||||||
}
|
|
||||||
|
|
||||||
void act(
|
void act(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
DiscoverPageAction action, {
|
DiscoverPageAction action, {
|
||||||
|
|||||||
@@ -56,14 +56,10 @@ class DiscoverPage extends StatefulWidget {
|
|||||||
const DiscoverPage({
|
const DiscoverPage({
|
||||||
super.key,
|
super.key,
|
||||||
this.active = true,
|
this.active = true,
|
||||||
this.searchRequest = 0,
|
|
||||||
this.focusSearch = false,
|
|
||||||
this.callbacks = const DiscoverPageCallbacks(),
|
this.callbacks = const DiscoverPageCallbacks(),
|
||||||
});
|
});
|
||||||
|
|
||||||
final bool active;
|
final bool active;
|
||||||
final int searchRequest;
|
|
||||||
final bool focusSearch;
|
|
||||||
final DiscoverPageCallbacks callbacks;
|
final DiscoverPageCallbacks callbacks;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -89,7 +85,6 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
|||||||
logic.scroll.addListener(_onScroll);
|
logic.scroll.addListener(_onScroll);
|
||||||
theater.addListener(_onTheaterChanged);
|
theater.addListener(_onTheaterChanged);
|
||||||
unawaited(theater.select(null));
|
unawaited(theater.select(null));
|
||||||
if (widget.focusSearch) _focusOnFirstFrame();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onScroll() {
|
void _onScroll() {
|
||||||
@@ -106,23 +101,6 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
|||||||
if (mounted) setState(() {});
|
if (mounted) setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _focusOnFirstFrame() {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
if (mounted && widget.active) logic.openSearch();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didUpdateWidget(covariant DiscoverPage oldWidget) {
|
|
||||||
super.didUpdateWidget(oldWidget);
|
|
||||||
logic.syncSearch(
|
|
||||||
active: widget.active,
|
|
||||||
request: widget.searchRequest,
|
|
||||||
previousRequest: oldWidget.searchRequest,
|
|
||||||
isActive: () => mounted && widget.active,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
logic.scroll.removeListener(_onScroll);
|
logic.scroll.removeListener(_onScroll);
|
||||||
|
|||||||
@@ -0,0 +1,256 @@
|
|||||||
|
// Author: fruit.com.cn
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import '../../core/data/repositories/search_repository.dart';
|
||||||
|
import '../../core/formatting/error_text.dart';
|
||||||
|
import '../../core/theme/app_colors.dart';
|
||||||
|
import '../../core/theme/app_dimens.dart';
|
||||||
|
import '../../core/theme/app_theme.dart';
|
||||||
|
import '../../core/widgets/empty_state.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
import '../home/widgets/home_feed_card.dart';
|
||||||
|
import '../player/open_player.dart';
|
||||||
|
import 'discover_binding.dart';
|
||||||
|
import 'discover_logic.dart';
|
||||||
|
|
||||||
|
class SearchPage extends StatefulWidget {
|
||||||
|
const SearchPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SearchPage> createState() => _SearchPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SearchPageState extends State<SearchPage> {
|
||||||
|
late final DiscoverLogic _logic;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_logic = DiscoverBinding().create(search: context.read<SearchRepository>());
|
||||||
|
_logic.addListener(_rebuild);
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (mounted) _logic.searchFocus.requestFocus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _rebuild() {
|
||||||
|
if (mounted) setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_logic
|
||||||
|
..removeListener(_rebuild)
|
||||||
|
..dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final l10n = AppLocalizations.of(context)!;
|
||||||
|
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||||
|
value: SystemUiOverlayStyle.light,
|
||||||
|
child: Theme(
|
||||||
|
data: AppTheme.dark(),
|
||||||
|
child: Scaffold(
|
||||||
|
key: const Key('search-page'),
|
||||||
|
backgroundColor: AppColors.appBackground,
|
||||||
|
body: SafeArea(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
_header(l10n),
|
||||||
|
Expanded(child: _body(l10n)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _header(AppLocalizations l10n) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(8, 6, 14, 8),
|
||||||
|
child: SizedBox(
|
||||||
|
height: AppDimens.searchHeight,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
SizedBox.square(
|
||||||
|
dimension: AppDimens.buttonMedium,
|
||||||
|
child: IconButton(
|
||||||
|
key: const Key('search-back'),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
onPressed: () => Navigator.of(context).maybePop(),
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.arrow_back_ios_new_rounded,
|
||||||
|
color: AppColors.textPrimary,
|
||||||
|
size: 22,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: AppDimens.spaceSm),
|
||||||
|
Expanded(child: _field(l10n)),
|
||||||
|
const SizedBox(width: AppDimens.spaceSm),
|
||||||
|
TextButton(
|
||||||
|
key: const Key('search-cancel'),
|
||||||
|
onPressed: () => Navigator.of(context).maybePop(),
|
||||||
|
child: Text(l10n.searchCancel),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _field(AppLocalizations l10n) {
|
||||||
|
final hasText = _logic.search.text.isNotEmpty;
|
||||||
|
return Container(
|
||||||
|
key: const Key('search-field'),
|
||||||
|
height: AppDimens.searchHeight,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: AppDimens.spaceMd),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.contentSurface,
|
||||||
|
borderRadius: BorderRadius.circular(AppDimens.searchRadius),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
Icons.search_rounded,
|
||||||
|
color: AppColors.textSecondary,
|
||||||
|
size: 18,
|
||||||
|
),
|
||||||
|
const SizedBox(width: AppDimens.spaceSm),
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
key: const Key('discover-search'),
|
||||||
|
controller: _logic.search,
|
||||||
|
focusNode: _logic.searchFocus,
|
||||||
|
autofocus: true,
|
||||||
|
onChanged: (_) => _logic.onSearchChanged(),
|
||||||
|
textInputAction: TextInputAction.search,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: AppColors.textPrimary,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
isDense: true,
|
||||||
|
hintText: l10n.searchDiscover,
|
||||||
|
hintStyle: const TextStyle(
|
||||||
|
color: AppColors.textSecondary,
|
||||||
|
fontSize: 13,
|
||||||
|
),
|
||||||
|
border: InputBorder.none,
|
||||||
|
enabledBorder: InputBorder.none,
|
||||||
|
focusedBorder: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (_logic.searchLoading)
|
||||||
|
const SizedBox.square(
|
||||||
|
dimension: 16,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2,
|
||||||
|
color: AppColors.textPrimary,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else if (hasText)
|
||||||
|
IconButton(
|
||||||
|
key: const Key('search-clear'),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
constraints: const BoxConstraints.tightFor(width: 24, height: 24),
|
||||||
|
onPressed: () {
|
||||||
|
_logic.search.clear();
|
||||||
|
_logic.onSearchChanged();
|
||||||
|
_logic.searchFocus.requestFocus();
|
||||||
|
},
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.close_rounded,
|
||||||
|
color: AppColors.textSecondary,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _body(AppLocalizations l10n) {
|
||||||
|
if (!_logic.hasSearchQuery) {
|
||||||
|
return EmptyState(
|
||||||
|
key: const Key('search-idle'),
|
||||||
|
icon: Icons.search_rounded,
|
||||||
|
title: l10n.searchDiscover,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (_logic.searchLoading) {
|
||||||
|
return const Center(
|
||||||
|
child: SizedBox.square(
|
||||||
|
key: Key('search-loading'),
|
||||||
|
dimension: 20,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2,
|
||||||
|
color: AppColors.textPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (_logic.searchError != null) {
|
||||||
|
return EmptyState(
|
||||||
|
key: const Key('search-error'),
|
||||||
|
icon: Icons.cloud_off_rounded,
|
||||||
|
title: errorText(l10n, _logic.searchError, l10n.searchFailed),
|
||||||
|
actionLabel: l10n.retry,
|
||||||
|
onAction: _logic.retrySearch,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (_logic.searchResults.isEmpty) {
|
||||||
|
return EmptyState(
|
||||||
|
key: const Key('search-empty'),
|
||||||
|
icon: Icons.search_off_rounded,
|
||||||
|
title: l10n.noSearchResults,
|
||||||
|
subtitle: l10n.emptyHint,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
final width =
|
||||||
|
(constraints.maxWidth -
|
||||||
|
AppDimens.feedPageHorizontal * 2 -
|
||||||
|
AppDimens.homeGridGap * 2) /
|
||||||
|
3;
|
||||||
|
final itemExtent =
|
||||||
|
width / AppDimens.cardCoverAspectRatio +
|
||||||
|
AppDimens.homeCardBodyExtent;
|
||||||
|
return GridView.builder(
|
||||||
|
key: const Key('search-results-grid'),
|
||||||
|
padding: const EdgeInsets.fromLTRB(
|
||||||
|
AppDimens.feedPageHorizontal,
|
||||||
|
AppDimens.spaceMd,
|
||||||
|
AppDimens.feedPageHorizontal,
|
||||||
|
AppDimens.spaceXl,
|
||||||
|
),
|
||||||
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
crossAxisCount: 3,
|
||||||
|
crossAxisSpacing: AppDimens.homeGridGap,
|
||||||
|
mainAxisSpacing: AppDimens.homeGridRowGap,
|
||||||
|
mainAxisExtent: itemExtent,
|
||||||
|
),
|
||||||
|
itemCount: _logic.searchResults.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final drama = _logic.searchResults[index];
|
||||||
|
return HomeFeedCard(
|
||||||
|
drama: drama,
|
||||||
|
onTap: () => unawaited(playDramaFromCatalog(context, drama)),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
import '../../../core/data/models/drama.dart';
|
import '../../../core/data/models/drama.dart';
|
||||||
import '../../../core/formatting/error_text.dart';
|
import '../../../core/formatting/error_text.dart';
|
||||||
|
import '../../../core/theme/app_colors.dart';
|
||||||
import '../../../core/widgets/empty_state.dart';
|
import '../../../core/widgets/empty_state.dart';
|
||||||
import '../../../l10n/app_localizations.dart';
|
import '../../../l10n/app_localizations.dart';
|
||||||
import 'drama_grid.dart';
|
import 'drama_grid.dart';
|
||||||
@@ -38,7 +39,15 @@ class DiscoverSearchResults extends StatelessWidget {
|
|||||||
if (loading) {
|
if (loading) {
|
||||||
return const Padding(
|
return const Padding(
|
||||||
padding: EdgeInsets.symmetric(vertical: 80),
|
padding: EdgeInsets.symmetric(vertical: 80),
|
||||||
child: Center(child: CircularProgressIndicator(strokeWidth: 2)),
|
child: Center(
|
||||||
|
child: SizedBox.square(
|
||||||
|
dimension: 20,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2,
|
||||||
|
color: AppColors.textPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
final message = error;
|
final message = error;
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ class HomeLogic extends ChangeNotifier {
|
|||||||
final scaffoldKey = GlobalKey<ScaffoldState>();
|
final scaffoldKey = GlobalKey<ScaffoldState>();
|
||||||
Drama currentDrama;
|
Drama currentDrama;
|
||||||
int tab = 0;
|
int tab = 0;
|
||||||
int searchRequest = 0;
|
|
||||||
int resumeRequest = 0;
|
int resumeRequest = 0;
|
||||||
int backRequest = 0;
|
int backRequest = 0;
|
||||||
int followingHistoryRequest = 0;
|
int followingHistoryRequest = 0;
|
||||||
@@ -31,10 +30,9 @@ class HomeLogic extends ChangeNotifier {
|
|||||||
if (!_disposed) notifyListeners();
|
if (!_disposed) notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void select(int nextTab, {bool search = false, bool resume = false}) {
|
void select(int nextTab, {bool resume = false}) {
|
||||||
FocusManager.instance.primaryFocus?.unfocus();
|
FocusManager.instance.primaryFocus?.unfocus();
|
||||||
tab = nextTab;
|
tab = nextTab;
|
||||||
if (search) searchRequest++;
|
|
||||||
if (resume) resumeRequest++;
|
if (resume) resumeRequest++;
|
||||||
notify();
|
notify();
|
||||||
}
|
}
|
||||||
@@ -81,7 +79,7 @@ class HomeLogic extends ChangeNotifier {
|
|||||||
case DiscoverPageAction.home:
|
case DiscoverPageAction.home:
|
||||||
select(0);
|
select(0);
|
||||||
case DiscoverPageAction.recommend:
|
case DiscoverPageAction.recommend:
|
||||||
select(1, search: true);
|
select(1);
|
||||||
case DiscoverPageAction.following:
|
case DiscoverPageAction.following:
|
||||||
select(2);
|
select(2);
|
||||||
case DiscoverPageAction.mine:
|
case DiscoverPageAction.mine:
|
||||||
@@ -112,7 +110,7 @@ class HomeLogic extends ChangeNotifier {
|
|||||||
case MyPageAction.home:
|
case MyPageAction.home:
|
||||||
select(0);
|
select(0);
|
||||||
case MyPageAction.recommend:
|
case MyPageAction.recommend:
|
||||||
select(1, search: true);
|
select(1);
|
||||||
case MyPageAction.following:
|
case MyPageAction.following:
|
||||||
select(2);
|
select(2);
|
||||||
case MyPageAction.mine:
|
case MyPageAction.mine:
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import '../../core/widgets/app_root_tab.dart';
|
|||||||
import '../../core/widgets/catalog_endpoint_editor.dart';
|
import '../../core/widgets/catalog_endpoint_editor.dart';
|
||||||
import '../../core/widgets/empty_state.dart';
|
import '../../core/widgets/empty_state.dart';
|
||||||
import '../../l10n/app_localizations.dart';
|
import '../../l10n/app_localizations.dart';
|
||||||
import '../discover/discover_view.dart';
|
import '../discover/search_page.dart';
|
||||||
import '../following/following_page.dart';
|
import '../following/following_page.dart';
|
||||||
import '../filter/filter_page.dart';
|
import '../filter/filter_page.dart';
|
||||||
import '../mine/mine_types.dart';
|
import '../mine/mine_types.dart';
|
||||||
@@ -292,9 +292,9 @@ class _HomeTabsState extends State<_HomeTabs> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _openSearch(BuildContext context) {
|
void _openSearch(BuildContext context) {
|
||||||
Navigator.of(context).push<void>(
|
Navigator.of(
|
||||||
MaterialPageRoute(builder: (_) => const DiscoverPage(focusSearch: true)),
|
context,
|
||||||
);
|
).push<void>(MaterialPageRoute(builder: (_) => const SearchPage()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _pushPage(BuildContext context, Widget page) {
|
void _pushPage(BuildContext context, Widget page) {
|
||||||
|
|||||||
@@ -26,8 +26,7 @@ import '../../core/formatting/localized_content.dart';
|
|||||||
import '../../core/theme/app_colors.dart';
|
import '../../core/theme/app_colors.dart';
|
||||||
import '../../core/navigation/fade_page_route.dart';
|
import '../../core/navigation/fade_page_route.dart';
|
||||||
import '../../l10n/app_localizations.dart';
|
import '../../l10n/app_localizations.dart';
|
||||||
import '../discover/discover_types.dart';
|
import '../discover/search_page.dart';
|
||||||
import '../discover/discover_view.dart';
|
|
||||||
import '../drama_detail/drama_detail_view.dart';
|
import '../drama_detail/drama_detail_view.dart';
|
||||||
import '../mine/mine_types.dart';
|
import '../mine/mine_types.dart';
|
||||||
import '../mine/mine_view.dart';
|
import '../mine/mine_view.dart';
|
||||||
@@ -1127,13 +1126,10 @@ class PlayerLogic extends ChangeNotifier {
|
|||||||
onSearch?.call();
|
onSearch?.call();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await showDiscover(context, focusSearch: true);
|
await showDiscover(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> showDiscover(
|
Future<void> showDiscover(BuildContext context) async {
|
||||||
BuildContext context, {
|
|
||||||
bool focusSearch = false,
|
|
||||||
}) async {
|
|
||||||
if (embedded) {
|
if (embedded) {
|
||||||
onDiscover?.call();
|
onDiscover?.call();
|
||||||
return;
|
return;
|
||||||
@@ -1142,13 +1138,10 @@ class PlayerLogic extends ChangeNotifier {
|
|||||||
await session.playbackSettled;
|
await session.playbackSettled;
|
||||||
await saveCurrentProgress();
|
await saveCurrentProgress();
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
final action = await Navigator.of(context).push<DiscoverPageAction>(
|
await Navigator.of(
|
||||||
MaterialPageRoute(builder: (_) => DiscoverPage(focusSearch: focusSearch)),
|
context,
|
||||||
);
|
).push<void>(MaterialPageRoute(builder: (_) => const SearchPage()));
|
||||||
if (!context.mounted) return;
|
if (context.mounted) {
|
||||||
if (action == DiscoverPageAction.mine) {
|
|
||||||
await showMy(context);
|
|
||||||
} else {
|
|
||||||
session.setRouteVisible(true);
|
session.setRouteVisible(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,44 +23,6 @@ void main() {
|
|||||||
logic.dispose();
|
logic.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('should_focus_search_only_for_new_request_on_active_page', (
|
|
||||||
tester,
|
|
||||||
) async {
|
|
||||||
final logic = DiscoverLogic();
|
|
||||||
await tester.pumpWidget(
|
|
||||||
MaterialApp(
|
|
||||||
home: Scaffold(
|
|
||||||
body: TextField(
|
|
||||||
controller: logic.search,
|
|
||||||
focusNode: logic.searchFocus,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
logic.syncSearch(
|
|
||||||
active: false,
|
|
||||||
request: 1,
|
|
||||||
previousRequest: 0,
|
|
||||||
isActive: () => false,
|
|
||||||
);
|
|
||||||
await tester.pump();
|
|
||||||
expect(logic.searchFocus.hasFocus, isFalse);
|
|
||||||
|
|
||||||
logic.syncSearch(
|
|
||||||
active: true,
|
|
||||||
request: 2,
|
|
||||||
previousRequest: 1,
|
|
||||||
isActive: () => true,
|
|
||||||
);
|
|
||||||
await tester.pump();
|
|
||||||
await tester.pump();
|
|
||||||
expect(logic.searchFocus.hasFocus, isTrue);
|
|
||||||
|
|
||||||
await tester.pumpWidget(const SizedBox.shrink());
|
|
||||||
logic.dispose();
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('should_forward_action_when_embedded_or_pop_when_standalone', (
|
testWidgets('should_forward_action_when_embedded_or_pop_when_standalone', (
|
||||||
tester,
|
tester,
|
||||||
) async {
|
) async {
|
||||||
|
|||||||
@@ -42,23 +42,17 @@ void main() {
|
|||||||
logic.select(1);
|
logic.select(1);
|
||||||
|
|
||||||
expect(logic.tab, 1);
|
expect(logic.tab, 1);
|
||||||
expect(logic.searchRequest, 0);
|
|
||||||
expect(logic.resumeRequest, 0);
|
expect(logic.resumeRequest, 0);
|
||||||
expect(logic.backRequest, 0);
|
expect(logic.backRequest, 0);
|
||||||
expect(notifications, 1);
|
expect(notifications, 1);
|
||||||
logic.dispose();
|
logic.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should_raise_only_its_own_counter_when_each_select_flag_is_used', () {
|
test('should_raise_only_resume_counter_when_select_flag_is_used', () {
|
||||||
final logic = HomeLogic(dramas);
|
final logic = HomeLogic(dramas);
|
||||||
|
|
||||||
logic.select(1, search: true);
|
|
||||||
expect(logic.searchRequest, 1);
|
|
||||||
expect(logic.resumeRequest, 0);
|
|
||||||
|
|
||||||
logic.select(0, resume: true);
|
logic.select(0, resume: true);
|
||||||
expect(logic.resumeRequest, 1);
|
expect(logic.resumeRequest, 1);
|
||||||
expect(logic.searchRequest, 1);
|
|
||||||
logic.dispose();
|
logic.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -161,7 +155,6 @@ void main() {
|
|||||||
|
|
||||||
logic.myAction(MyPageAction.recommend);
|
logic.myAction(MyPageAction.recommend);
|
||||||
expect(logic.tab, 1);
|
expect(logic.tab, 1);
|
||||||
expect(logic.searchRequest, 1);
|
|
||||||
|
|
||||||
logic.myAction(MyPageAction.following);
|
logic.myAction(MyPageAction.following);
|
||||||
expect(logic.tab, 2);
|
expect(logic.tab, 2);
|
||||||
|
|||||||
@@ -118,6 +118,10 @@ void main() {
|
|||||||
await _pumpApp(tester);
|
await _pumpApp(tester);
|
||||||
await tester.tap(find.byKey(const Key('home-search-entry')));
|
await tester.tap(find.byKey(const Key('home-search-entry')));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.byKey(const Key('search-page')), findsOneWidget);
|
||||||
|
expect(find.byKey(const Key('search-back')), findsOneWidget);
|
||||||
|
expect(find.byKey(const Key('search-cancel')), findsOneWidget);
|
||||||
|
expect(find.byType(AppBottomNav), findsNothing);
|
||||||
expect(
|
expect(
|
||||||
tester
|
tester
|
||||||
.widget<TextField>(find.byKey(const Key('discover-search')))
|
.widget<TextField>(find.byKey(const Key('discover-search')))
|
||||||
@@ -125,7 +129,7 @@ void main() {
|
|||||||
?.hasFocus,
|
?.hasFocus,
|
||||||
isTrue,
|
isTrue,
|
||||||
);
|
);
|
||||||
await tester.binding.handlePopRoute();
|
await tester.tap(find.byKey(const Key('search-cancel')));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
await tester.tap(find.byKey(const Key('tab-recommend')));
|
await tester.tap(find.byKey(const Key('tab-recommend')));
|
||||||
@@ -133,6 +137,20 @@ void main() {
|
|||||||
await tester.tap(find.byKey(const Key('recommend-search')));
|
await tester.tap(find.byKey(const Key('recommend-search')));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
expect(find.byKey(const Key('discover-search')), findsOneWidget);
|
expect(find.byKey(const Key('discover-search')), findsOneWidget);
|
||||||
|
await tester.enterText(find.byKey(const Key('discover-search')), 'signal');
|
||||||
|
await tester.pump(const Duration(milliseconds: 400));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.byKey(const Key('search-results-grid')), findsOneWidget);
|
||||||
|
final results = tester.widget<GridView>(
|
||||||
|
find.byKey(const Key('search-results-grid')),
|
||||||
|
);
|
||||||
|
final delegate =
|
||||||
|
results.gridDelegate as SliverGridDelegateWithFixedCrossAxisCount;
|
||||||
|
expect(delegate.crossAxisCount, 3);
|
||||||
|
await tester.tap(find.byKey(const Key('search-back')));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.byKey(const Key('recommend-page')), findsOneWidget);
|
||||||
|
expect(find.byKey(const Key('recommend-tab-recommend')), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('should_open_player_from_home_grid_card', (tester) async {
|
testWidgets('should_open_player_from_home_grid_card', (tester) async {
|
||||||
@@ -188,6 +206,7 @@ Future<void> _pumpApp(
|
|||||||
playerFactory: createWidgetPlayer,
|
playerFactory: createWidgetPlayer,
|
||||||
catalogRepository: catalog,
|
catalogRepository: catalog,
|
||||||
categoryRepository: FakeCategoryRepository(catalog: catalog),
|
categoryRepository: FakeCategoryRepository(catalog: catalog),
|
||||||
|
searchRepository: catalog,
|
||||||
watchHistoryRepository: InMemoryWatchHistoryRepository(),
|
watchHistoryRepository: InMemoryWatchHistoryRepository(),
|
||||||
favoriteRepository: InMemoryFavoriteRepository(cards: catalog.dramas),
|
favoriteRepository: InMemoryFavoriteRepository(cards: catalog.dramas),
|
||||||
playbackRepository: FakePlaybackRepository(),
|
playbackRepository: FakePlaybackRepository(),
|
||||||
|
|||||||
Reference in new issue
Block a user