Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions lib/pages/collect/collect_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ abstract class _CollectController with Store {
ObservableList<CollectedBangumi> collectibles =
ObservableList<CollectedBangumi>();

@observable
String searchText = '';

@observable
bool isSearching = false;

void loadCollectibles() {
collectibles.clear();
collectibles.addAll(_collectCrudRepository.getAllCollectibles());
Expand Down
36 changes: 35 additions & 1 deletion lib/pages/collect/collect_controller.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

122 changes: 120 additions & 2 deletions lib/pages/collect/collect_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,13 @@ class CollectPage extends StatefulWidget {
class _CollectPageState extends State<CollectPage>
with SingleTickerProviderStateMixin {
final CollectController collectController = Modular.get<CollectController>();
final TextEditingController searchController = TextEditingController();
late NavigationBarState navigationBarState;
TabController? tabController;
bool showDelete = false;
bool syncCollectiblesing = false;
bool searchBarHovering = false;
late final FocusNode _searchEntryFocusNode = FocusNode();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: _searchEntryFocusNode is created here but never disposed. The dispose() method (lines 162-166) disposes tabController and searchController, but not this FocusNode. An undisposed FocusNode leaks resources and is flagged by the Flutter analyzer.

Suggested change
late final FocusNode _searchEntryFocusNode = FocusNode();
late final FocusNode _searchEntryFocusNode = FocusNode();

Then add _searchEntryFocusNode.dispose(); inside dispose().


Reply with @kilocode-bot fix it to have Kilo Code address this issue.


Future<bool> _syncBangumiWithProgress({
required GlobalKey<_FullSyncProgressDialogState> progressDialogKey,
Expand Down Expand Up @@ -133,6 +136,7 @@ class _CollectPageState extends State<CollectPage>
}

void onBackPressed(BuildContext context) {
collectController.searchText = '';
if (syncCollectiblesing) {
return;
}
Expand All @@ -147,6 +151,7 @@ class _CollectPageState extends State<CollectPage>
@override
void initState() {
super.initState();
searchController.text = collectController.searchText;
collectController.loadCollectibles();
tabController = TabController(vsync: this, length: tabs.length);
navigationBarState =
Expand All @@ -156,6 +161,7 @@ class _CollectPageState extends State<CollectPage>
@override
void dispose() {
tabController?.dispose();
searchController.dispose();
super.dispose();
}

Expand Down Expand Up @@ -191,6 +197,16 @@ class _CollectPageState extends State<CollectPage>
),
title: const Text('追番'),
actions: [
AnimatedSwitcher(
duration: const Duration(milliseconds: 500),
transitionBuilder: (Widget child, Animation<double> animation) {
return ScaleTransition(scale: animation, child: child);
},
child: Observer(builder: (context) => Padding(
padding: const EdgeInsets.only(right: 8.0),
child: searchEntry
)),
),
IconButton(
onPressed: () {
setState(() {
Expand Down Expand Up @@ -247,8 +263,8 @@ class _CollectPageState extends State<CollectPage>
: const Icon(Icons.sync_rounded),
),
body: Observer(builder: (context) {
return renderBody;
}),
return renderBody;
}),
),
);
}
Expand All @@ -274,6 +290,12 @@ class _CollectPageState extends State<CollectPage>
collectedBangumiRenderItemList[element.type - 1].add(element);
}
for (List<CollectedBangumi> list in collectedBangumiRenderItemList) {
if (collectController.searchText.isNotEmpty) {
list.removeWhere((item) {
return !item.bangumiItem.nameCn.contains(collectController.searchText) &&

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Case-sensitive search may miss matches

String.contains is case-sensitive, so searching naruto will not match a title stored as Naruto. For the English name field in particular, users likely expect case-insensitive matching. Consider lower-casing both sides, e.g. normalize searchText once and compare against nameCn.toLowerCase() / name.toLowerCase().


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

!item.bangumiItem.name.contains(collectController.searchText);
});
}
list.sort((a, b) => b.time.millisecondsSinceEpoch
.compareTo(a.time.millisecondsSinceEpoch));
}
Expand Down Expand Up @@ -351,6 +373,102 @@ class _CollectPageState extends State<CollectPage>
}
return gridViewList;
}

Widget get searchEntry {
if (collectController.isSearching) {
return TapRegion(
groupId: 'searchEntryTapRegion',
child: Observer(builder:(context) {
return SizedBox(
height: 48,
width: MediaQuery.sizeOf(context).width * 0.6,
child: SearchBar(
autoFocus: true,
focusNode: _searchEntryFocusNode,
controller: searchController,
hintText: '在收藏中搜索番剧喵~',
hintStyle: WidgetStateProperty.all(
Theme.of(context).textTheme.bodyLarge?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
elevation: WidgetStateProperty.all(2.0),
shadowColor: WidgetStateProperty.all(
Theme.of(context).colorScheme.shadow,
),
surfaceTintColor: WidgetStateProperty.all(
Theme.of(context).colorScheme.surfaceTint,
),
padding: WidgetStateProperty.all(
const EdgeInsets.symmetric(horizontal: 4.0),
),
shape: WidgetStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(28.0),
),
),
leading: const Padding(
padding: EdgeInsets.only(left: 8.0),
child: Icon(Icons.search_rounded),
),
trailing: [
Observer(builder: (context) => searchEntryActionButton),
],
onChanged: (value) {
collectController.searchText = value;
},
));
}),
onTapOutside: (_) {
collectController.isSearching = false;
},
);
} else {
IconButton searchButton = IconButton(
onPressed: () {
collectController.isSearching = true;
},
icon: const Icon(Icons.search_rounded),
);

if (collectController.searchText.isNotEmpty) {
return Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Row(
children: [
Text(
'搜索: ${collectController.searchText}',
style: Theme.of(context).textTheme.bodyMedium,
),
searchButton,
],
),
);
} else {
return searchButton;
}
}
}

Widget get searchEntryActionButton {
if (collectController.searchText.isNotEmpty) {
return IconButton(
padding: const EdgeInsets.all(0),
alignment: Alignment.centerRight,
style: ButtonStyle(
overlayColor: WidgetStateProperty.all(Colors.transparent),
),
onPressed: () {
searchController.clear();
collectController.searchText = '';
_searchEntryFocusNode.requestFocus();
},
icon: const Icon(Icons.close_rounded),
);
} else {
return const SizedBox.shrink();
}
}
}

class _FullSyncProgressDialog extends StatefulWidget {
Expand Down
Loading