feat(app): 搜索改为调用后端并渲染卡片结果
This commit is contained in:
1 parent
730e38622f
commit
ade0cc3566
13 files changed
+424
-20
No files matched your search
@@ -1,20 +1,29 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../core/data/models/drama.dart';
|
||||
import '../../core/data/models/watch_progress.dart';
|
||||
import '../../core/data/remote/api_exception.dart';
|
||||
import '../../core/data/repositories/search_repository.dart';
|
||||
import '../../core/data/repositories/watch_history_repository.dart';
|
||||
import '../../l10n/app_localizations.dart';
|
||||
|
||||
enum DiscoverPageAction { home, my, watchlist, detail, resume }
|
||||
|
||||
class DiscoverLogic extends ChangeNotifier {
|
||||
DiscoverLogic({required this.history, required this.drama}) {
|
||||
DiscoverLogic({
|
||||
required this.history,
|
||||
required this.drama,
|
||||
this.searchRepository,
|
||||
}) {
|
||||
refreshPosition();
|
||||
}
|
||||
|
||||
final WatchHistoryRepository history;
|
||||
final SearchRepository? searchRepository;
|
||||
Drama drama;
|
||||
late Future<Duration?> position;
|
||||
WatchProgress? resumeRecord;
|
||||
@@ -22,6 +31,17 @@ class DiscoverLogic extends ChangeNotifier {
|
||||
final FocusNode searchFocus = FocusNode();
|
||||
final ScrollController scroll = ScrollController();
|
||||
bool searchOpen = false;
|
||||
|
||||
/// 搜索防抖与分页口径:与后端 `/app/search` 一致。
|
||||
Duration searchDebounce = const Duration(milliseconds: 350);
|
||||
static const int searchPageSize = 20;
|
||||
List<Drama> searchResults = const [];
|
||||
int searchTotal = 0;
|
||||
bool searchLoading = false;
|
||||
String? searchError;
|
||||
Timer? _searchTimer;
|
||||
String _lastRequestedQuery = '';
|
||||
int _searchRequest = 0;
|
||||
bool _disposed = false;
|
||||
|
||||
void notify() {
|
||||
@@ -51,20 +71,89 @@ class DiscoverLogic extends ChangeNotifier {
|
||||
notify();
|
||||
}
|
||||
|
||||
void onSearchChanged() => notify();
|
||||
bool get hasSearchQuery => search.text.trim().isNotEmpty;
|
||||
|
||||
List<Drama> filtered(List<Drama> dramas) {
|
||||
final query = search.text.trim().toLowerCase();
|
||||
if (query.isEmpty) return dramas;
|
||||
return dramas.where((drama) {
|
||||
final haystack = [
|
||||
drama.title,
|
||||
drama.genre,
|
||||
drama.category,
|
||||
drama.synopsis,
|
||||
].join(' ').toLowerCase();
|
||||
return haystack.contains(query);
|
||||
}).toList();
|
||||
/// 输入变化只触发后端查询;本地不再做标题/简介匹配。
|
||||
void onSearchChanged() {
|
||||
final query = search.text.trim();
|
||||
if (query.isEmpty) {
|
||||
_resetSearch();
|
||||
notify();
|
||||
return;
|
||||
}
|
||||
notify();
|
||||
_scheduleSearch(query);
|
||||
}
|
||||
|
||||
/// 搜索态展示后端结果,非搜索态展示剧场自身的可见短剧。
|
||||
List<Drama> visibleDramas(List<Drama> fallback) {
|
||||
if (!hasSearchQuery) return fallback;
|
||||
return searchResults;
|
||||
}
|
||||
|
||||
Future<void> retrySearch() async {
|
||||
final query = search.text.trim();
|
||||
if (query.isEmpty) return;
|
||||
await _runSearch(query);
|
||||
}
|
||||
|
||||
void _resetSearch() {
|
||||
_searchTimer?.cancel();
|
||||
_searchRequest++;
|
||||
searchResults = const [];
|
||||
searchTotal = 0;
|
||||
searchLoading = false;
|
||||
searchError = null;
|
||||
_lastRequestedQuery = '';
|
||||
}
|
||||
|
||||
void _scheduleSearch(String query) {
|
||||
final alreadyRequested = query == _lastRequestedQuery;
|
||||
if (alreadyRequested && (searchLoading || searchResults.isNotEmpty || searchError != null)) {
|
||||
return;
|
||||
}
|
||||
_lastRequestedQuery = query;
|
||||
_searchTimer?.cancel();
|
||||
if (searchDebounce == Duration.zero) {
|
||||
unawaited(_runSearch(query));
|
||||
return;
|
||||
}
|
||||
_searchTimer = Timer(searchDebounce, () => unawaited(_runSearch(query)));
|
||||
}
|
||||
|
||||
Future<void> _runSearch(String query) async {
|
||||
final repository = searchRepository;
|
||||
if (repository == null) return;
|
||||
final request = ++_searchRequest;
|
||||
searchLoading = true;
|
||||
searchError = null;
|
||||
notify();
|
||||
try {
|
||||
final result = await repository.search(
|
||||
query,
|
||||
pageIndex: 1,
|
||||
pageSize: searchPageSize,
|
||||
);
|
||||
if (request != _searchRequest) return;
|
||||
searchResults = result.list;
|
||||
searchTotal = result.total;
|
||||
} on ApiException catch (exception) {
|
||||
_applySearchError(request, exception.message);
|
||||
} catch (_) {
|
||||
_applySearchError(request, 'searchFailed');
|
||||
} finally {
|
||||
if (request == _searchRequest) {
|
||||
searchLoading = false;
|
||||
notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _applySearchError(int request, String message) {
|
||||
if (request != _searchRequest) return;
|
||||
searchError = message;
|
||||
searchResults = const [];
|
||||
searchTotal = 0;
|
||||
}
|
||||
|
||||
Drama? resumeDrama(List<Drama> dramas) {
|
||||
@@ -165,6 +254,7 @@ class DiscoverLogic extends ChangeNotifier {
|
||||
@override
|
||||
void dispose() {
|
||||
_disposed = true;
|
||||
_searchTimer?.cancel();
|
||||
search.dispose();
|
||||
searchFocus.dispose();
|
||||
scroll.dispose();
|
||||
|
||||
Reference in new issue
Block a user