Files
video/app/lib/providers/search_provider.dart
T
2026-10-15 20:36:59 +08:00

156 lines
4.1 KiB
Dart

import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../core/storage/search_history_store.dart';
import '../models/video_item.dart';
import '../services/search_service.dart';
/// 搜索排序(《03-接口文档》5.10)
enum SearchSort {
hot('hot', '热度'),
latest('latest', '最新');
const SearchSort(this.apiValue, this.label);
final String apiValue;
final String label;
}
/// 搜索页状态
class SearchState {
const SearchState({
this.keyword = '',
this.sort = SearchSort.hot,
this.items = const [],
this.total = 0,
this.page = 1,
this.isSearching = false,
this.isLoadingMore = false,
this.searched = false,
this.hotWords = const [],
this.history = const [],
});
final String keyword;
final SearchSort sort;
final List<VideoItem> items;
final int total;
final int page;
final bool isSearching;
final bool isLoadingMore;
/// 是否已执行过搜索(区分默认页与结果页)
final bool searched;
/// 热搜榜(进入页面时加载)
final List<String> hotWords;
/// 本地搜索历史(最新在前)
final List<String> history;
bool get hasMore => items.length < total;
SearchState copyWith({
String? keyword,
SearchSort? sort,
List<VideoItem>? items,
int? total,
int? page,
bool? isSearching,
bool? isLoadingMore,
bool? searched,
List<String>? hotWords,
List<String>? history,
}) =>
SearchState(
keyword: keyword ?? this.keyword,
sort: sort ?? this.sort,
items: items ?? this.items,
total: total ?? this.total,
page: page ?? this.page,
isSearching: isSearching ?? this.isSearching,
isLoadingMore: isLoadingMore ?? this.isLoadingMore,
searched: searched ?? this.searched,
hotWords: hotWords ?? this.hotWords,
history: history ?? this.history,
);
}
class SearchNotifier extends StateNotifier<SearchState> {
SearchNotifier() : super(SearchState(history: SearchHistoryStore.I.history));
/// 加载热搜榜(静默失败,空列表兜底)
Future<void> loadHot() async {
try {
final words = await SearchService.hotWords();
state = state.copyWith(hotWords: words);
} catch (_) {}
}
/// 执行搜索(换关键词或排序时全量重来)
Future<void> search(String keyword, {SearchSort? sort}) async {
final word = keyword.trim();
if (word.isEmpty) return;
final nextSort = sort ?? state.sort;
state = state.copyWith(
keyword: word,
sort: nextSort,
isSearching: true,
searched: true,
items: const [],
total: 0,
page: 1,
);
// 写入本地历史(去重置顶,最多 10 条)
await SearchHistoryStore.I.add(word);
state = state.copyWith(history: SearchHistoryStore.I.history);
try {
final page =
await SearchService.search(word, sort: nextSort.apiValue);
state = state.copyWith(
items: page.items,
total: page.total,
isSearching: false,
page: 1,
);
} catch (_) {
state = state.copyWith(isSearching: false);
}
}
/// 触底加载更多
Future<void> loadMore() async {
if (!state.hasMore || state.isLoadingMore || state.isSearching) return;
state = state.copyWith(isLoadingMore: true);
try {
final page = await SearchService.search(
state.keyword,
sort: state.sort.apiValue,
page: state.page + 1,
);
state = state.copyWith(
items: [...state.items, ...page.items],
total: page.total,
isLoadingMore: false,
page: state.page + 1,
);
} catch (_) {
state = state.copyWith(isLoadingMore: false);
}
}
/// 删除单条历史
Future<void> removeHistory(String word) async {
await SearchHistoryStore.I.remove(word);
state = state.copyWith(history: SearchHistoryStore.I.history);
}
/// 清空历史
Future<void> clearHistory() async {
await SearchHistoryStore.I.clear();
state = state.copyWith(history: const []);
}
}
final searchProvider =
StateNotifierProvider<SearchNotifier, SearchState>((ref) => SearchNotifier());