generated from dellevin/template
优化搜索界面
This commit is contained in:
@@ -85,7 +85,7 @@ class _StrollPageState extends State<StrollPage> {
|
|||||||
subtitle: n.tags.take(3).join(' · '),
|
subtitle: n.tags.take(3).join(' · '),
|
||||||
detail: n.content,
|
detail: n.content,
|
||||||
imagePath: n.images.isNotEmpty ? n.images.first : null,
|
imagePath: n.images.isNotEmpty ? n.images.first : null,
|
||||||
icon: Icons.note_outlined, label: '笔记',
|
icon: Icons.sticky_note_2_outlined, label: '笔记',
|
||||||
createdAt: n.createdAt,
|
createdAt: n.createdAt,
|
||||||
tags: n.tags.take(3).toList(),
|
tags: n.tags.take(3).toList(),
|
||||||
color: const Color(0xFF66BB6A),
|
color: const Color(0xFF66BB6A),
|
||||||
@@ -376,7 +376,7 @@ class _StrollPageState extends State<StrollPage> {
|
|||||||
('all', '全部', Icons.apps_outlined),
|
('all', '全部', Icons.apps_outlined),
|
||||||
('movie', '影视', Icons.movie_outlined),
|
('movie', '影视', Icons.movie_outlined),
|
||||||
('book', '书籍', Icons.menu_book_outlined),
|
('book', '书籍', Icons.menu_book_outlined),
|
||||||
('note', '笔记', Icons.note_outlined),
|
('note', '笔记', Icons.sticky_note_2_outlined),
|
||||||
('game', '游戏', Icons.sports_esports_outlined),
|
('game', '游戏', Icons.sports_esports_outlined),
|
||||||
];
|
];
|
||||||
return Padding(
|
return Padding(
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ class _DesktopHomePageState extends State<DesktopHomePage> {
|
|||||||
if (_showNotes) {
|
if (_showNotes) {
|
||||||
final words = fn.fold<int>(0, (sum, n) => sum + n.content.length);
|
final words = fn.fold<int>(0, (sum, n) => sum + n.content.length);
|
||||||
final ws = words >= 10000 ? '${(words / 10000).toStringAsFixed(1)}万' : '$words';
|
final ws = words >= 10000 ? '${(words / 10000).toStringAsFixed(1)}万' : '$words';
|
||||||
items.add(_ModuleData(Icons.note_outlined, '笔记', fn.length, '$ws字', _noteColor, 2));
|
items.add(_ModuleData(Icons.sticky_note_2_outlined, '笔记', fn.length, '$ws字', _noteColor, 2));
|
||||||
}
|
}
|
||||||
if (_showGames) {
|
if (_showGames) {
|
||||||
final completed = fg.where((g) => g.status == 'completed').length;
|
final completed = fg.where((g) => g.status == 'completed').length;
|
||||||
|
|||||||
@@ -307,8 +307,8 @@ class _DesktopIconRail extends StatelessWidget {
|
|||||||
static const _categoryMeta = [
|
static const _categoryMeta = [
|
||||||
(Icons.movie_outlined, Icons.movie, '影视', 0, Color(0xFF2563EB)),
|
(Icons.movie_outlined, Icons.movie, '影视', 0, Color(0xFF2563EB)),
|
||||||
(Icons.menu_book_outlined, Icons.menu_book, '阅读', 1, Color(0xFF16A34A)),
|
(Icons.menu_book_outlined, Icons.menu_book, '阅读', 1, Color(0xFF16A34A)),
|
||||||
(Icons.note_outlined, Icons.note, '笔记', 2, Color(0xFF9333EA)),
|
|
||||||
(Icons.sports_esports_outlined, Icons.sports_esports, '游戏', 3, Color(0xFFEA580C)),
|
(Icons.sports_esports_outlined, Icons.sports_esports, '游戏', 3, Color(0xFFEA580C)),
|
||||||
|
(Icons.sticky_note_2_outlined, Icons.sticky_note_2, '笔记', 2, Color(0xFF9333EA)),
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -880,7 +880,7 @@ class _StrollDialogState extends State<_StrollDialog> {
|
|||||||
for (final n in provider.notes.where((n) => !n.isDeleted)) {
|
for (final n in provider.notes.where((n) => !n.isDeleted)) {
|
||||||
notePool.add(_StrollItem(type: 'note', data: n, id: 'n_${n.id}', title: n.title.isNotEmpty ? n.title : '随手记',
|
notePool.add(_StrollItem(type: 'note', data: n, id: 'n_${n.id}', title: n.title.isNotEmpty ? n.title : '随手记',
|
||||||
subtitle: n.tags.take(3).join(' · '), detail: n.content, imagePath: n.images.isNotEmpty ? n.images.first : null,
|
subtitle: n.tags.take(3).join(' · '), detail: n.content, imagePath: n.images.isNotEmpty ? n.images.first : null,
|
||||||
icon: Icons.note_outlined, label: '笔记', createdAt: n.createdAt,
|
icon: Icons.sticky_note_2_outlined, label: '笔记', createdAt: n.createdAt,
|
||||||
tags: n.tags.take(3).toList(), color: const Color(0xFF66BB6A)));
|
tags: n.tags.take(3).toList(), color: const Color(0xFF66BB6A)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1758,7 +1758,7 @@ class _TagManagementDialogState extends State<_TagManagementDialog> {
|
|||||||
|
|
||||||
static const _tabTypes = ['movie_genre', 'book_genre', 'note_tag', 'game_genre'];
|
static const _tabTypes = ['movie_genre', 'book_genre', 'note_tag', 'game_genre'];
|
||||||
static const _typeLabels = ['影视类型', '书籍类型', '笔记标签', '游戏类型'];
|
static const _typeLabels = ['影视类型', '书籍类型', '笔记标签', '游戏类型'];
|
||||||
static const _typeIcons = [Icons.movie_outlined, Icons.menu_book_outlined, Icons.note_outlined, Icons.sports_esports_outlined];
|
static const _typeIcons = [Icons.movie_outlined, Icons.menu_book_outlined, Icons.sticky_note_2_outlined, Icons.sports_esports_outlined];
|
||||||
|
|
||||||
final Map<String, List<Map<String, dynamic>>> _tagCache = {};
|
final Map<String, List<Map<String, dynamic>>> _tagCache = {};
|
||||||
Map<String, int> _usageCounts = {};
|
Map<String, int> _usageCounts = {};
|
||||||
@@ -3918,7 +3918,7 @@ class _DesktopListPanelState extends State<_DesktopListPanel> {
|
|||||||
if (a.isPinned != b.isPinned) return a.isPinned ? -1 : 1;
|
if (a.isPinned != b.isPinned) return a.isPinned ? -1 : 1;
|
||||||
return b.createdAt.compareTo(a.createdAt);
|
return b.createdAt.compareTo(a.createdAt);
|
||||||
});
|
});
|
||||||
if (items.isEmpty) return _buildEmpty('暂无笔记记录', Icons.note_outlined);
|
if (items.isEmpty) return _buildEmpty('暂无笔记记录', Icons.sticky_note_2_outlined);
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||||
itemCount: items.length,
|
itemCount: items.length,
|
||||||
@@ -4096,7 +4096,7 @@ class _DesktopListPanelState extends State<_DesktopListPanel> {
|
|||||||
subtitle: n.content.length > 40 ? '${n.content.substring(0, 40)}...' : null,
|
subtitle: n.content.length > 40 ? '${n.content.substring(0, 40)}...' : null,
|
||||||
imagePath: null,
|
imagePath: null,
|
||||||
accentColor: const Color(0xFF9333EA),
|
accentColor: const Color(0xFF9333EA),
|
||||||
icon: Icons.note_outlined,
|
icon: Icons.sticky_note_2_outlined,
|
||||||
selected: provider.selectedNote?.id == n.id,
|
selected: provider.selectedNote?.id == n.id,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
provider.setMainTabIndex(2);
|
provider.setMainTabIndex(2);
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ import '../movies/movie_tab_page.dart';
|
|||||||
import '../book/book_tab_page.dart';
|
import '../book/book_tab_page.dart';
|
||||||
import '../note/note_tab_page.dart';
|
import '../note/note_tab_page.dart';
|
||||||
import '../game/game_tab_page.dart';
|
import '../game/game_tab_page.dart';
|
||||||
import '../online_search/search_page.dart';
|
import '../online_search/search_hub_page.dart';
|
||||||
import '../online_search/online_search_page.dart';
|
|
||||||
import '../sync/webdav_sync_page.dart';
|
import '../sync/webdav_sync_page.dart';
|
||||||
|
|
||||||
/// 主内容页 - 观影/阅读/笔记标签页(PageView 滑动切换)
|
/// 主内容页 - 观影/阅读/笔记标签页(PageView 滑动切换)
|
||||||
@@ -71,8 +70,8 @@ class _MainContentPageState extends State<MainContentPage> {
|
|||||||
final tabs = <_TabItem>[];
|
final tabs = <_TabItem>[];
|
||||||
if (_showMovieTab) tabs.add(_TabItem('影视', 0));
|
if (_showMovieTab) tabs.add(_TabItem('影视', 0));
|
||||||
if (_showBookTab) tabs.add(_TabItem('阅读', 1));
|
if (_showBookTab) tabs.add(_TabItem('阅读', 1));
|
||||||
if (_showNoteTab) tabs.add(_TabItem('笔记', 2));
|
|
||||||
if (_showGameTab) tabs.add(_TabItem('游戏', 3));
|
if (_showGameTab) tabs.add(_TabItem('游戏', 3));
|
||||||
|
if (_showNoteTab) tabs.add(_TabItem('笔记', 2));
|
||||||
return tabs;
|
return tabs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,30 +118,7 @@ class _MainContentPageState extends State<MainContentPage> {
|
|||||||
_buildCloudSyncButton(context),
|
_buildCloudSyncButton(context),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.search),
|
icon: const Icon(Icons.search),
|
||||||
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const SearchPage())),
|
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const SearchHubPage())),
|
||||||
),
|
|
||||||
if (UserPrefs().enhancedSearchEnabled)
|
|
||||||
IconButton(
|
|
||||||
icon: Stack(
|
|
||||||
clipBehavior: Clip.none,
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.search, size: 22),
|
|
||||||
Positioned(
|
|
||||||
right: -3,
|
|
||||||
top: -3,
|
|
||||||
child: Container(
|
|
||||||
width: 12,
|
|
||||||
height: 12,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: colors.surface,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
child: Icon(Icons.add, size: 10, color: colors.onSurface),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const OnlineSearchPage())),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -431,8 +407,8 @@ class _MainContentPageState extends State<MainContentPage> {
|
|||||||
children: [
|
children: [
|
||||||
if (_showMovieTab) const MovieTabPage(),
|
if (_showMovieTab) const MovieTabPage(),
|
||||||
if (_showBookTab) const BookTabPage(),
|
if (_showBookTab) const BookTabPage(),
|
||||||
if (_showNoteTab) const NoteTabPage(),
|
|
||||||
if (_showGameTab) const GameTabPage(),
|
if (_showGameTab) const GameTabPage(),
|
||||||
|
if (_showNoteTab) const NoteTabPage(),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -443,7 +419,7 @@ class _MainContentPageState extends State<MainContentPage> {
|
|||||||
switch (label) {
|
switch (label) {
|
||||||
case '影视': return Icons.movie_outlined;
|
case '影视': return Icons.movie_outlined;
|
||||||
case '阅读': return Icons.menu_book_outlined;
|
case '阅读': return Icons.menu_book_outlined;
|
||||||
case '笔记': return Icons.note_outlined;
|
case '笔记': return Icons.sticky_note_2_outlined;
|
||||||
case '游戏': return Icons.sports_esports_outlined;
|
case '游戏': return Icons.sports_esports_outlined;
|
||||||
default: return Icons.circle;
|
default: return Icons.circle;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ class _NoteTabPageState extends State<NoteTabPage> {
|
|||||||
? NoteAddPage(onCancel: () => provider.cancelAdding())
|
? NoteAddPage(onCancel: () => provider.cancelAdding())
|
||||||
: provider.selectedNote != null
|
: provider.selectedNote != null
|
||||||
? NoteDetailPage(note: provider.selectedNote!, embedded: true)
|
? NoteDetailPage(note: provider.selectedNote!, embedded: true)
|
||||||
: const DetailPlaceholder(icon: Icons.note_outlined, message: '选择一条笔记查看详情');
|
: const DetailPlaceholder(icon: Icons.sticky_note_2_outlined, message: '选择一条笔记查看详情');
|
||||||
return MasterDetailScaffold(
|
return MasterDetailScaffold(
|
||||||
master: masterContent,
|
master: masterContent,
|
||||||
detail: detailWidget,
|
detail: detailWidget,
|
||||||
@@ -417,7 +417,7 @@ class _NoteTabPageState extends State<NoteTabPage> {
|
|||||||
return Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
return Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||||
Container(width: 80, height: 80,
|
Container(width: 80, height: 80,
|
||||||
decoration: BoxDecoration(color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(20)),
|
decoration: BoxDecoration(color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(20)),
|
||||||
child: Icon(Icons.note_outlined, size: 40, color: colors.onSurface.withValues(alpha: 0.25))),
|
child: Icon(Icons.sticky_note_2_outlined, size: 40, color: colors.onSurface.withValues(alpha: 0.25))),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
Text('暂无笔记', style: TextStyle(fontSize: 16, color: colors.onSurface.withValues(alpha: 0.4))),
|
Text('暂无笔记', style: TextStyle(fontSize: 16, color: colors.onSurface.withValues(alpha: 0.4))),
|
||||||
]));
|
]));
|
||||||
|
|||||||
@@ -7,15 +7,34 @@ import '../../utils/toast_util.dart';
|
|||||||
import 'movie_detail_page.dart';
|
import 'movie_detail_page.dart';
|
||||||
import 'book_detail_page.dart';
|
import 'book_detail_page.dart';
|
||||||
|
|
||||||
/// 在线搜索影视/书籍
|
/// 在线搜索影视/书籍(带 Scaffold + AppBar)
|
||||||
class OnlineSearchPage extends StatefulWidget {
|
class OnlineSearchPage extends StatelessWidget {
|
||||||
const OnlineSearchPage({super.key});
|
const OnlineSearchPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<OnlineSearchPage> createState() => _OnlineSearchPageState();
|
Widget build(BuildContext context) {
|
||||||
|
final colors = Theme.of(context).colorScheme;
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: colors.surface,
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('在线搜索'),
|
||||||
|
elevation: 0,
|
||||||
|
scrolledUnderElevation: 0,
|
||||||
|
),
|
||||||
|
body: const OnlineSearchPageBody(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _OnlineSearchPageState extends State<OnlineSearchPage> {
|
/// 在线搜索 body —— 可嵌入到统一搜索页 SearchHubPage
|
||||||
|
class OnlineSearchPageBody extends StatefulWidget {
|
||||||
|
const OnlineSearchPageBody({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<OnlineSearchPageBody> createState() => _OnlineSearchPageBodyState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _OnlineSearchPageBodyState extends State<OnlineSearchPageBody> {
|
||||||
final _searchController = TextEditingController();
|
final _searchController = TextEditingController();
|
||||||
final _focusNode = FocusNode();
|
final _focusNode = FocusNode();
|
||||||
final _movieScrollController = ScrollController();
|
final _movieScrollController = ScrollController();
|
||||||
@@ -252,39 +271,45 @@ class _OnlineSearchPageState extends State<OnlineSearchPage> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final colors = Theme.of(context).colorScheme;
|
final colors = Theme.of(context).colorScheme;
|
||||||
return Scaffold(
|
return Material(
|
||||||
backgroundColor: colors.surface,
|
color: colors.surface,
|
||||||
appBar: AppBar(
|
child: Column(
|
||||||
titleSpacing: 8,
|
children: [
|
||||||
title: _buildSearchBar(colors),
|
// 搜索栏 + 搜索按钮
|
||||||
actions: [
|
Padding(
|
||||||
TextButton(
|
padding: const EdgeInsets.fromLTRB(12, 8, 8, 8),
|
||||||
onPressed: _doSearch,
|
child: Row(
|
||||||
child: Text('搜索',
|
children: [
|
||||||
style: TextStyle(fontSize: 14, color: colors.primary)),
|
Expanded(child: _buildSearchBar(colors)),
|
||||||
|
TextButton(
|
||||||
|
onPressed: _doSearch,
|
||||||
|
child: Text('搜索',
|
||||||
|
style: TextStyle(fontSize: 14, color: colors.primary)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// 影视/书籍 tab 栏(搜索后显示)
|
||||||
|
if (_hasSearched)
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
bottom: BorderSide(
|
||||||
|
color: colors.outlineVariant, width: 0.5))),
|
||||||
|
child: Row(children: [
|
||||||
|
_buildTabButton(colors, '影视', 0, _movieTotal),
|
||||||
|
_buildTabButton(colors, '书籍', 1, _bookTotal),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: _hasSearched
|
||||||
|
? (_currentTab == 0
|
||||||
|
? _buildMovieResults(colors)
|
||||||
|
: _buildBookResults(colors))
|
||||||
|
: _buildHistoryPanel(colors),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
bottom: _hasSearched
|
|
||||||
? PreferredSize(
|
|
||||||
preferredSize: const Size.fromHeight(40),
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border(
|
|
||||||
bottom: BorderSide(
|
|
||||||
color: colors.outlineVariant, width: 0.5))),
|
|
||||||
child: Row(children: [
|
|
||||||
_buildTabButton(colors, '影视', 0, _movieTotal),
|
|
||||||
_buildTabButton(colors, '书籍', 1, _bookTotal),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
body: _hasSearched
|
|
||||||
? (_currentTab == 0
|
|
||||||
? _buildMovieResults(colors)
|
|
||||||
: _buildBookResults(colors))
|
|
||||||
: _buildHistoryPanel(colors),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
92
lib/pages/online_search/search_hub_page.dart
Normal file
92
lib/pages/online_search/search_hub_page.dart
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import '../../utils/user_prefs.dart';
|
||||||
|
import 'search_page.dart';
|
||||||
|
import 'online_search_page.dart';
|
||||||
|
|
||||||
|
/// 统一搜索页 —— 本地搜索 / 增强搜索(在线)切换
|
||||||
|
class SearchHubPage extends StatefulWidget {
|
||||||
|
const SearchHubPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SearchHubPage> createState() => _SearchHubPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SearchHubPageState extends State<SearchHubPage> {
|
||||||
|
late bool _isOnline;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_isOnline = UserPrefs().lastSearchOnline;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _switchTo(bool online) {
|
||||||
|
if (!mounted || _isOnline == online) return;
|
||||||
|
setState(() => _isOnline = online);
|
||||||
|
UserPrefs().setLastSearchOnline(online);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final colors = Theme.of(context).colorScheme;
|
||||||
|
final showToggle = UserPrefs().enhancedSearchEnabled;
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('搜索'),
|
||||||
|
elevation: 0,
|
||||||
|
scrolledUnderElevation: 0,
|
||||||
|
actions: [
|
||||||
|
if (showToggle) _buildToggle(colors),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: IndexedStack(
|
||||||
|
index: _isOnline ? 1 : 0,
|
||||||
|
children: const [
|
||||||
|
SearchPageBody(),
|
||||||
|
OnlineSearchPageBody(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildToggle(ColorScheme colors) {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.only(right: 8),
|
||||||
|
padding: const EdgeInsets.all(2),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: colors.surfaceContainerHighest.withValues(alpha: 0.6),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
_toggleBtn('本地', !_isOnline, () => _switchTo(false), colors),
|
||||||
|
_toggleBtn('增强', _isOnline, () => _switchTo(true), colors),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _toggleBtn(String label, bool selected, VoidCallback onTap, ColorScheme colors) {
|
||||||
|
return Material(
|
||||||
|
color: selected ? colors.primary : Colors.transparent,
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: onTap,
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: selected ? FontWeight.w600 : FontWeight.w400,
|
||||||
|
color: selected ? colors.onPrimary : colors.onSurface.withValues(alpha: 0.55),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,15 +9,34 @@ import '../note/note_detail_page.dart';
|
|||||||
import '../game/game_detail_page.dart';
|
import '../game/game_detail_page.dart';
|
||||||
import '../../widgets/fade_in_local_image.dart';
|
import '../../widgets/fade_in_local_image.dart';
|
||||||
|
|
||||||
/// 搜索页面
|
/// 搜索页面(带 Scaffold + AppBar)
|
||||||
class SearchPage extends StatefulWidget {
|
class SearchPage extends StatelessWidget {
|
||||||
const SearchPage({super.key});
|
const SearchPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SearchPage> createState() => _SearchPageState();
|
Widget build(BuildContext context) {
|
||||||
|
final colors = Theme.of(context).colorScheme;
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: colors.surface,
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('搜索'),
|
||||||
|
elevation: 0,
|
||||||
|
scrolledUnderElevation: 0,
|
||||||
|
),
|
||||||
|
body: const SearchPageBody(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SearchPageState extends State<SearchPage> {
|
/// 搜索页 body —— 可嵌入到统一搜索页 SearchHubPage
|
||||||
|
class SearchPageBody extends StatefulWidget {
|
||||||
|
const SearchPageBody({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SearchPageBody> createState() => _SearchPageBodyState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SearchPageBodyState extends State<SearchPageBody> {
|
||||||
final _searchController = TextEditingController();
|
final _searchController = TextEditingController();
|
||||||
final _focusNode = FocusNode();
|
final _focusNode = FocusNode();
|
||||||
|
|
||||||
@@ -33,12 +52,6 @@ class _SearchPageState extends State<SearchPage> {
|
|||||||
|
|
||||||
Timer? _debounce;
|
Timer? _debounce;
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => _focusNode.requestFocus());
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_searchController.dispose();
|
_searchController.dispose();
|
||||||
@@ -167,14 +180,9 @@ class _SearchPageState extends State<SearchPage> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final colors = Theme.of(context).colorScheme;
|
final colors = Theme.of(context).colorScheme;
|
||||||
return Scaffold(
|
return Material(
|
||||||
backgroundColor: colors.surface,
|
color: colors.surface,
|
||||||
appBar: AppBar(
|
child: Column(children: [
|
||||||
title: const Text('搜索'),
|
|
||||||
elevation: 0,
|
|
||||||
scrolledUnderElevation: 0,
|
|
||||||
),
|
|
||||||
body: Column(children: [
|
|
||||||
_buildSearchBar(),
|
_buildSearchBar(),
|
||||||
_buildFilterRow(),
|
_buildFilterRow(),
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -239,7 +247,7 @@ class _SearchPageState extends State<SearchPage> {
|
|||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(child: _filterChip('书籍', Icons.menu_book_outlined, _showBooks, bookCount, () { setState(() { _showBooks = !_showBooks; _performSearch(); }); })),
|
Expanded(child: _filterChip('书籍', Icons.menu_book_outlined, _showBooks, bookCount, () { setState(() { _showBooks = !_showBooks; _performSearch(); }); })),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(child: _filterChip('笔记', Icons.note_outlined, _showNotes, noteCount, () { setState(() { _showNotes = !_showNotes; _performSearch(); }); })),
|
Expanded(child: _filterChip('笔记', Icons.sticky_note_2_outlined, _showNotes, noteCount, () { setState(() { _showNotes = !_showNotes; _performSearch(); }); })),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(child: _filterChip('游戏', Icons.sports_esports_outlined, _showGames, gameCount, () { setState(() { _showGames = !_showGames; _performSearch(); }); })),
|
Expanded(child: _filterChip('游戏', Icons.sports_esports_outlined, _showGames, gameCount, () { setState(() { _showGames = !_showGames; _performSearch(); }); })),
|
||||||
]),
|
]),
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ class _FeatureSettingsPageState extends State<FeatureSettingsPage> {
|
|||||||
all.addAll([
|
all.addAll([
|
||||||
(0, '影视', Icons.movie_outlined),
|
(0, '影视', Icons.movie_outlined),
|
||||||
(1, '阅读', Icons.menu_book_outlined),
|
(1, '阅读', Icons.menu_book_outlined),
|
||||||
(2, '笔记', Icons.note_outlined),
|
(2, '笔记', Icons.sticky_note_2_outlined),
|
||||||
(3, '游戏', Icons.sports_esports_outlined),
|
(3, '游戏', Icons.sports_esports_outlined),
|
||||||
].where((t) {
|
].where((t) {
|
||||||
return switch (t.$1) {
|
return switch (t.$1) {
|
||||||
@@ -196,7 +196,7 @@ class _FeatureSettingsPageState extends State<FeatureSettingsPage> {
|
|||||||
indent: 24,
|
indent: 24,
|
||||||
endIndent: 24,
|
endIndent: 24,
|
||||||
color: colors.outlineVariant),
|
color: colors.outlineVariant),
|
||||||
_buildSwitchItem(Icons.note_outlined, '笔记', '记录和管理笔记', _showNoteTab,
|
_buildSwitchItem(Icons.sticky_note_2_outlined, '笔记', '记录和管理笔记', _showNoteTab,
|
||||||
_toggleNoteTab),
|
_toggleNoteTab),
|
||||||
Divider(
|
Divider(
|
||||||
height: 0.5,
|
height: 0.5,
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ class _LayoutSettingsPageState extends State<LayoutSettingsPage> {
|
|||||||
onTap: _showBookSheet,
|
onTap: _showBookSheet,
|
||||||
),
|
),
|
||||||
_buildCategoryTile(
|
_buildCategoryTile(
|
||||||
icon: Icons.note_outlined,
|
icon: Icons.sticky_note_2_outlined,
|
||||||
title: '笔记',
|
title: '笔记',
|
||||||
color: colors.primary,
|
color: colors.primary,
|
||||||
subtitle: _noteSubtitle,
|
subtitle: _noteSubtitle,
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class _TagManagementPageState extends State<TagManagementPage> {
|
|||||||
|
|
||||||
static const _tabTypes = ['movie_genre', 'book_genre', 'note_tag', 'game_genre'];
|
static const _tabTypes = ['movie_genre', 'book_genre', 'note_tag', 'game_genre'];
|
||||||
static const _typeLabels = ['影视类型', '书籍类型', '笔记标签', '游戏类型'];
|
static const _typeLabels = ['影视类型', '书籍类型', '笔记标签', '游戏类型'];
|
||||||
static const _typeIcons = [Icons.movie_outlined, Icons.menu_book_outlined, Icons.note_outlined, Icons.sports_esports_outlined];
|
static const _typeIcons = [Icons.movie_outlined, Icons.menu_book_outlined, Icons.sticky_note_2_outlined, Icons.sports_esports_outlined];
|
||||||
|
|
||||||
final Map<String, List<Map<String, dynamic>>> _tagCache = {};
|
final Map<String, List<Map<String, dynamic>>> _tagCache = {};
|
||||||
Map<String, int> _usageCounts = {};
|
Map<String, int> _usageCounts = {};
|
||||||
|
|||||||
@@ -284,6 +284,10 @@ class UserPrefs {
|
|||||||
bool get enhancedSearchEnabled => prefs.getBool('enhancedSearchEnabled') ?? false;
|
bool get enhancedSearchEnabled => prefs.getBool('enhancedSearchEnabled') ?? false;
|
||||||
Future<bool> setEnhancedSearchEnabled(bool value) => prefs.setBool('enhancedSearchEnabled', value);
|
Future<bool> setEnhancedSearchEnabled(bool value) => prefs.setBool('enhancedSearchEnabled', value);
|
||||||
|
|
||||||
|
/// 上次使用的搜索模式:false=本地, true=增强
|
||||||
|
bool get lastSearchOnline => prefs.getBool('lastSearchOnline') ?? false;
|
||||||
|
Future<bool> setLastSearchOnline(bool value) => prefs.setBool('lastSearchOnline', value);
|
||||||
|
|
||||||
/// 影视增强搜索 Token
|
/// 影视增强搜索 Token
|
||||||
String get movieSearchToken => prefs.getString('movieSearchToken') ?? '';
|
String get movieSearchToken => prefs.getString('movieSearchToken') ?? '';
|
||||||
Future<bool> setMovieSearchToken(String value) => prefs.setString('movieSearchToken', value);
|
Future<bool> setMovieSearchToken(String value) => prefs.setString('movieSearchToken', value);
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ void showAddSheet(BuildContext context, AppProvider provider) {
|
|||||||
if (userPrefs.showNoteTab) {
|
if (userPrefs.showNoteTab) {
|
||||||
options.add(_buildOption(
|
options.add(_buildOption(
|
||||||
colors: colors,
|
colors: colors,
|
||||||
icon: Icons.note_outlined,
|
icon: Icons.sticky_note_2_outlined,
|
||||||
title: '添加笔记',
|
title: '添加笔记',
|
||||||
subtitle: '记录你的想法和笔记',
|
subtitle: '记录你的想法和笔记',
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class _AddTypeDialog extends StatelessWidget {
|
|||||||
final types = <_AddTypeItem>[];
|
final types = <_AddTypeItem>[];
|
||||||
if (showMovie) types.add(_AddTypeItem('影视', Icons.movie_outlined, 0));
|
if (showMovie) types.add(_AddTypeItem('影视', Icons.movie_outlined, 0));
|
||||||
if (showBook) types.add(_AddTypeItem('阅读', Icons.menu_book_outlined, 1));
|
if (showBook) types.add(_AddTypeItem('阅读', Icons.menu_book_outlined, 1));
|
||||||
if (showNote) types.add(_AddTypeItem('笔记', Icons.note_outlined, 2));
|
if (showNote) types.add(_AddTypeItem('笔记', Icons.sticky_note_2_outlined, 2));
|
||||||
if (showGame) types.add(_AddTypeItem('游戏', Icons.sports_esports_outlined, 3));
|
if (showGame) types.add(_AddTypeItem('游戏', Icons.sports_esports_outlined, 3));
|
||||||
|
|
||||||
return Dialog(
|
return Dialog(
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ class _CustomDrawerState extends State<CustomDrawer> {
|
|||||||
final statItems = <(IconData, int, String, Color)>[];
|
final statItems = <(IconData, int, String, Color)>[];
|
||||||
if (userPrefs.showMovieTab) statItems.add((Icons.movie_outlined, movieCount, '观影', const Color(0xFF2563EB)));
|
if (userPrefs.showMovieTab) statItems.add((Icons.movie_outlined, movieCount, '观影', const Color(0xFF2563EB)));
|
||||||
if (userPrefs.showBookTab) statItems.add((Icons.menu_book_outlined, bookCount, '阅读', const Color(0xFF16A34A)));
|
if (userPrefs.showBookTab) statItems.add((Icons.menu_book_outlined, bookCount, '阅读', const Color(0xFF16A34A)));
|
||||||
if (userPrefs.showNoteTab) statItems.add((Icons.note_outlined, noteCount, '笔记', const Color(0xFF9333EA)));
|
if (userPrefs.showNoteTab) statItems.add((Icons.sticky_note_2_outlined, noteCount, '笔记', const Color(0xFF9333EA)));
|
||||||
if (userPrefs.showGameTab) statItems.add((Icons.sports_esports_outlined, gameCount, '游戏', const Color(0xFFEA580C)));
|
if (userPrefs.showGameTab) statItems.add((Icons.sports_esports_outlined, gameCount, '游戏', const Color(0xFFEA580C)));
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
@@ -657,7 +657,7 @@ class _CustomDrawerState extends State<CustomDrawer> {
|
|||||||
for (final b in dayBooks) _dayDetailItem(ctx, Icons.menu_book_outlined, b.title, const Color(0xFF16A34A)),
|
for (final b in dayBooks) _dayDetailItem(ctx, Icons.menu_book_outlined, b.title, const Color(0xFF16A34A)),
|
||||||
],
|
],
|
||||||
if (dayNotes.isNotEmpty) ...[
|
if (dayNotes.isNotEmpty) ...[
|
||||||
for (final n in dayNotes) _dayDetailItem(ctx, Icons.note_outlined, n.title.isNotEmpty ? n.title : '随手记', const Color(0xFF9333EA)),
|
for (final n in dayNotes) _dayDetailItem(ctx, Icons.sticky_note_2_outlined, n.title.isNotEmpty ? n.title : '随手记', const Color(0xFF9333EA)),
|
||||||
],
|
],
|
||||||
if (dayGames.isNotEmpty) ...[
|
if (dayGames.isNotEmpty) ...[
|
||||||
for (final g in dayGames) _dayDetailItem(ctx, Icons.sports_esports_outlined, g.title, const Color(0xFFEA580C)),
|
for (final g in dayGames) _dayDetailItem(ctx, Icons.sports_esports_outlined, g.title, const Color(0xFFEA580C)),
|
||||||
@@ -803,7 +803,7 @@ class _CustomDrawerState extends State<CustomDrawer> {
|
|||||||
borderRadius: BorderRadius.circular(5),
|
borderRadius: BorderRadius.circular(5),
|
||||||
),
|
),
|
||||||
child: Icon(
|
child: Icon(
|
||||||
item.type == 'movie' ? Icons.movie_outlined : item.type == 'book' ? Icons.menu_book_outlined : item.type == 'game' ? Icons.sports_esports_outlined : Icons.note_outlined,
|
item.type == 'movie' ? Icons.movie_outlined : item.type == 'book' ? Icons.menu_book_outlined : item.type == 'game' ? Icons.sports_esports_outlined : Icons.sticky_note_2_outlined,
|
||||||
size: 12, color: _typeColor(item.type),
|
size: 12, color: _typeColor(item.type),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user