From a5331564830d2d45ac23fd449faa90c958d59cfa Mon Sep 17 00:00:00 2001 From: chetanr25 <1ds22ai010@dsce.edu.com> Date: Wed, 1 Jan 2025 01:15:50 +0530 Subject: [PATCH] Add Search Functionality and Fix Project List Display Bug --- .../components/search_text_field.dart | 33 ++++ .../components/search_toggle_button.dart | 28 +++ lib/ui/pages/landing_page/landing_page.dart | 176 +++++++++++------- 3 files changed, 174 insertions(+), 63 deletions(-) create mode 100644 lib/ui/pages/landing_page/components/search_text_field.dart create mode 100644 lib/ui/pages/landing_page/components/search_toggle_button.dart diff --git a/lib/ui/pages/landing_page/components/search_text_field.dart b/lib/ui/pages/landing_page/components/search_text_field.dart new file mode 100644 index 00000000..521cd075 --- /dev/null +++ b/lib/ui/pages/landing_page/components/search_text_field.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:paintroid/ui/theme/theme.dart'; + +class SearchTextField extends StatelessWidget { + final TextEditingController controller; + final FocusNode focusNode; + final ValueChanged onChanged; + + const SearchTextField({ + super.key, + required this.controller, + required this.focusNode, + required this.onChanged, + }); + + @override + Widget build(BuildContext context) { + return TextField( + controller: controller, + focusNode: focusNode, + autofocus: true, + style: TextStyle(color: PaintroidTheme.of(context).onSurfaceColor), + decoration: InputDecoration( + hintText: 'Search projects...', + hintStyle: TextStyle( + color: PaintroidTheme.of(context).onSurfaceColor.withOpacity(0.6), + ), + border: InputBorder.none, + ), + onChanged: onChanged, + ); + } +} diff --git a/lib/ui/pages/landing_page/components/search_toggle_button.dart b/lib/ui/pages/landing_page/components/search_toggle_button.dart new file mode 100644 index 00000000..1cd08fc1 --- /dev/null +++ b/lib/ui/pages/landing_page/components/search_toggle_button.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; + +class SearchToggleButton extends StatelessWidget { + final bool isSearchActive; + final VoidCallback onSearchStart; + final VoidCallback onSearchEnd; + + const SearchToggleButton({ + super.key, + required this.isSearchActive, + required this.onSearchStart, + required this.onSearchEnd, + }); + + @override + Widget build(BuildContext context) { + if (isSearchActive) { + return IconButton( + icon: const Icon(Icons.close), + onPressed: onSearchEnd, + ); + } + return IconButton( + icon: const Icon(Icons.search), + onPressed: onSearchStart, + ); + } +} diff --git a/lib/ui/pages/landing_page/landing_page.dart b/lib/ui/pages/landing_page/landing_page.dart index fa481f23..475e4c0f 100644 --- a/lib/ui/pages/landing_page/landing_page.dart +++ b/lib/ui/pages/landing_page/landing_page.dart @@ -19,6 +19,8 @@ import 'package:paintroid/ui/pages/landing_page/components/image_preview.dart'; import 'package:paintroid/ui/pages/landing_page/components/main_overflow_menu.dart'; import 'package:paintroid/ui/pages/landing_page/components/project_list_tile.dart'; import 'package:paintroid/ui/pages/landing_page/components/project_overflow_menu.dart'; +import 'package:paintroid/ui/pages/landing_page/components/search_toggle_button.dart'; +import 'package:paintroid/ui/pages/landing_page/components/search_text_field.dart'; import 'package:paintroid/ui/shared/icon_svg.dart'; import 'package:paintroid/ui/theme/theme.dart'; import 'package:paintroid/ui/utils/toast_utils.dart'; @@ -37,6 +39,18 @@ class _LandingPageState extends ConsumerState { late IFileService fileService; late IImageService imageService; + bool _isSearchActive = false; + String _searchQuery = ''; + final TextEditingController _searchController = TextEditingController(); + final FocusNode _searchFocusNode = FocusNode(); + + @override + void dispose() { + _searchController.dispose(); + _searchFocusNode.dispose(); + super.dispose(); + } + Future> _getProjects() async { return database.projectDAO.getProjects(); } @@ -80,6 +94,14 @@ class _LandingPageState extends ConsumerState { } } + List _filterProjects(List projects) { + if (_searchQuery.isEmpty) return projects; + return projects + .where((project) => + project.name.toLowerCase().contains(_searchQuery.toLowerCase())) + .toList(); + } + @override Widget build(BuildContext context) { ToastContext().init(context); @@ -99,34 +121,63 @@ class _LandingPageState extends ConsumerState { return Scaffold( backgroundColor: PaintroidTheme.of(context).primaryColor, appBar: AppBar( - title: Text(widget.title), - actions: const [MainOverflowMenu()], + title: _isSearchActive + ? SearchTextField( + controller: _searchController, + focusNode: _searchFocusNode, + onChanged: (value) { + setState(() { + _searchQuery = value; + }); + }, + ) + : Text(widget.title), + actions: [ + SearchToggleButton( + isSearchActive: _isSearchActive, + onSearchStart: () { + setState(() { + _isSearchActive = true; + }); + }, + onSearchEnd: () { + setState(() { + _isSearchActive = false; + _searchQuery = ''; + _searchController.clear(); + }); + }, + ), + if (!_isSearchActive) const MainOverflowMenu(), + ], ), body: FutureBuilder( future: _getProjects(), builder: (BuildContext context, AsyncSnapshot> snapshot) { if (snapshot.connectionState == ConnectionState.done && snapshot.hasData) { - if (snapshot.data!.isNotEmpty) { - latestModifiedProject = snapshot.data![0]; + final filteredProjects = _filterProjects(snapshot.data!); + if (filteredProjects.isNotEmpty) { + latestModifiedProject = filteredProjects[0]; } return Column( children: [ - Flexible( - flex: 2, - child: _ProjectPreview( - ioHandler: ioHandler, - imageService: imageService, - latestModifiedProject: latestModifiedProject, - onProjectPreviewTap: () { - if (latestModifiedProject != null) { - _openProject(latestModifiedProject, ioHandler, ref); - } else { - _clearCanvas(); - _navigateToPocketPaint(); - } - }), - ), + if (!_isSearchActive) + Flexible( + flex: 2, + child: _ProjectPreview( + ioHandler: ioHandler, + imageService: imageService, + latestModifiedProject: latestModifiedProject, + onProjectPreviewTap: () { + if (latestModifiedProject != null) { + _openProject(latestModifiedProject, ioHandler, ref); + } else { + _clearCanvas(); + _navigateToPocketPaint(); + } + }), + ), Container( color: PaintroidTheme.of(context).primaryContainerColor, padding: const EdgeInsets.all(20), @@ -146,21 +197,18 @@ class _LandingPageState extends ConsumerState { flex: 3, child: ListView.builder( itemBuilder: (context, index) { - if (index != 0) { - Project project = snapshot.data![index]; - return ProjectListTile( - project: project, - imageService: imageService, - index: index, - onTap: () async { - _clearCanvas(); - _openProject(project, ioHandler, ref); - }, - ); - } - return Container(); + Project project = filteredProjects[index]; + return ProjectListTile( + project: project, + imageService: imageService, + index: index, + onTap: () async { + _clearCanvas(); + _openProject(project, ioHandler, ref); + }, + ); }, - itemCount: snapshot.data?.length, + itemCount: filteredProjects.length, ), ), ], @@ -174,36 +222,38 @@ class _LandingPageState extends ConsumerState { } }, ), - floatingActionButton: Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - CustomActionButton( - heroTag: 'import_image', - icon: Icons.file_download, - hint: 'Load image', - onPressed: () async { - final bool imageLoaded = - await ioHandler.loadImage(context, this, false); - if (imageLoaded && mounted) { - _navigateToPocketPaint(); - } - }, - ), - const SizedBox( - height: 10, - ), - CustomActionButton( - key: const ValueKey(WidgetIdentifier.newImageActionButton), - heroTag: 'new_image', - icon: Icons.add, - hint: 'New image', - onPressed: () async { - _clearCanvas(); - _navigateToPocketPaint(); - }, - ), - ], - ), + floatingActionButton: _isSearchActive + ? null + : Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + CustomActionButton( + heroTag: 'import_image', + icon: Icons.file_download, + hint: 'Load image', + onPressed: () async { + final bool imageLoaded = + await ioHandler.loadImage(context, this, false); + if (imageLoaded && mounted) { + _navigateToPocketPaint(); + } + }, + ), + const SizedBox( + height: 10, + ), + CustomActionButton( + key: const ValueKey(WidgetIdentifier.newImageActionButton), + heroTag: 'new_image', + icon: Icons.add, + hint: 'New image', + onPressed: () async { + _clearCanvas(); + _navigateToPocketPaint(); + }, + ), + ], + ), ); } }