From 459ab83eb2c993a0ceab399e5925d61eb5c21ae0 Mon Sep 17 00:00:00 2001 From: Sudhar08 <99385366+sudhar08@users.noreply.github.com> Date: Fri, 22 Mar 2024 21:27:39 +0530 Subject: [PATCH 1/7] feat: Add a search/filter for collection pane #305 This feature adds a search/filter functionality to the collection pane, allowing users to easily search for specific requests based on their names. Users can input search queries to filter the requests displayed in the collection pane, improving the navigation and organization of requests within the application. --- lib/providers/collection_providers.dart | 35 +++++++++++++++++++++- lib/screens/home_page/collection_pane.dart | 34 ++++++++++++++++++--- 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/lib/providers/collection_providers.dart b/lib/providers/collection_providers.dart index db6b7cf3..30801ecb 100644 --- a/lib/providers/collection_providers.dart +++ b/lib/providers/collection_providers.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'settings_providers.dart'; import 'ui_providers.dart'; @@ -8,6 +9,7 @@ import '../consts.dart'; import 'package:http/http.dart' as http; final selectedIdStateProvider = StateProvider((ref) => null); +final searchQueryProvider = StateProvider((ref) => ''); final selectedRequestModelProvider = StateProvider((ref) { final selectedId = ref.watch(selectedIdStateProvider); @@ -31,7 +33,7 @@ final StateNotifierProvider?> class CollectionStateNotifier extends StateNotifier?> { CollectionStateNotifier(this.ref, this.hiveHandler) : super(null) { - var status = loadData(); + var status = filterRequests(ref.read(searchQueryProvider.notifier).state); Future.microtask(() { if (status) { ref.read(requestSequenceProvider.notifier).state = [ @@ -248,6 +250,37 @@ class CollectionStateNotifier } } + bool filterRequests(String query) { + if (query.isEmpty) { + + loadData(); + return true; + } else { + // Filter requests based on the query + final filteredRequests = state?.values.where((request) => + request.name.toLowerCase().contains(query.toLowerCase())).toList(); + + + if (filteredRequests != null && filteredRequests.isNotEmpty) { + + Map requestMap = {}; + + for (var request in filteredRequests) { + requestMap[request.id] = request; + } + + state = requestMap; + + } else { + print("No matching requests found"); + } + + + + return false; + } + } + Future saveData() async { ref.read(saveDataStateProvider.notifier).state = true; final saveResponse = ref.read(settingsProvider).saveResponses; diff --git a/lib/screens/home_page/collection_pane.dart b/lib/screens/home_page/collection_pane.dart index fd0b518a..c44ed56e 100644 --- a/lib/screens/home_page/collection_pane.dart +++ b/lib/screens/home_page/collection_pane.dart @@ -1,3 +1,4 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:apidash/providers/providers.dart'; @@ -66,10 +67,33 @@ class CollectionPane extends ConsumerWidget { style: kTextStyleButton, ), ), + ], ), ), - kVSpacer8, + kVSpacer10, + SizedBox( + height:30, + child:SearchBar( + + onChanged: (value){ + if(value.isNotEmpty){ + ref.read(searchQueryProvider.notifier).state = value; + ref.read(collectionStateNotifierProvider.notifier) + .filterRequests(value); + } + else{ + + } + + }, + hintText: "search", + leading: Icon(Icons.search), + textStyle: MaterialStateTextStyle.resolveWith((states) => TextStyle(fontSize: 15.0)), + elevation: MaterialStateProperty.all(2.0), + )), + kVSpacer10, + const Expanded( child: RequestList(), ), @@ -110,7 +134,7 @@ class _RequestListState extends ConsumerState { final alwaysShowCollectionPaneScrollbar = ref.watch(settingsProvider .select((value) => value.alwaysShowCollectionPaneScrollbar)); - return Scrollbar( + return requestItems.isNotEmpty?Scrollbar( controller: controller, thumbVisibility: alwaysShowCollectionPaneScrollbar ? true : null, radius: const Radius.circular(12), @@ -118,7 +142,7 @@ class _RequestListState extends ConsumerState { padding: kPr8CollectionPane, scrollController: controller, buildDefaultDragHandles: false, - itemCount: requestSequence.length, + itemCount: requestItems.length, onReorder: (int oldIndex, int newIndex) { if (oldIndex < newIndex) { newIndex -= 1; @@ -131,6 +155,8 @@ class _RequestListState extends ConsumerState { }, itemBuilder: (context, index) { var id = requestSequence[index]; + print(requestItems[id]!); + return ReorderableDragStartListener( key: ValueKey(id), index: index, @@ -144,7 +170,7 @@ class _RequestListState extends ConsumerState { ); }, ), - ); + ):Text("data"); } } From 5a9dd7c07e98304fc4b6594d6376aa61fdf232d1 Mon Sep 17 00:00:00 2001 From: Ankit Mahato Date: Sun, 24 Mar 2024 01:18:28 +0530 Subject: [PATCH 2/7] Refactor --- lib/providers/collection_providers.dart | 35 +------------ lib/providers/ui_providers.dart | 2 + lib/screens/home_page/collection_pane.dart | 61 +++++++++++++--------- lib/widgets/textfields.dart | 31 +++++++++-- 4 files changed, 66 insertions(+), 63 deletions(-) diff --git a/lib/providers/collection_providers.dart b/lib/providers/collection_providers.dart index 30801ecb..db6b7cf3 100644 --- a/lib/providers/collection_providers.dart +++ b/lib/providers/collection_providers.dart @@ -1,4 +1,3 @@ -import 'package:collection/collection.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'settings_providers.dart'; import 'ui_providers.dart'; @@ -9,7 +8,6 @@ import '../consts.dart'; import 'package:http/http.dart' as http; final selectedIdStateProvider = StateProvider((ref) => null); -final searchQueryProvider = StateProvider((ref) => ''); final selectedRequestModelProvider = StateProvider((ref) { final selectedId = ref.watch(selectedIdStateProvider); @@ -33,7 +31,7 @@ final StateNotifierProvider?> class CollectionStateNotifier extends StateNotifier?> { CollectionStateNotifier(this.ref, this.hiveHandler) : super(null) { - var status = filterRequests(ref.read(searchQueryProvider.notifier).state); + var status = loadData(); Future.microtask(() { if (status) { ref.read(requestSequenceProvider.notifier).state = [ @@ -250,37 +248,6 @@ class CollectionStateNotifier } } - bool filterRequests(String query) { - if (query.isEmpty) { - - loadData(); - return true; - } else { - // Filter requests based on the query - final filteredRequests = state?.values.where((request) => - request.name.toLowerCase().contains(query.toLowerCase())).toList(); - - - if (filteredRequests != null && filteredRequests.isNotEmpty) { - - Map requestMap = {}; - - for (var request in filteredRequests) { - requestMap[request.id] = request; - } - - state = requestMap; - - } else { - print("No matching requests found"); - } - - - - return false; - } - } - Future saveData() async { ref.read(saveDataStateProvider.notifier).state = true; final saveResponse = ref.read(settingsProvider).saveResponses; diff --git a/lib/providers/ui_providers.dart b/lib/providers/ui_providers.dart index 2fddb2ad..62f83f70 100644 --- a/lib/providers/ui_providers.dart +++ b/lib/providers/ui_providers.dart @@ -23,3 +23,5 @@ final nameTextFieldFocusNodeProvider = }); return focusNode; }); + +final searchQueryProvider = StateProvider((ref) => ''); diff --git a/lib/screens/home_page/collection_pane.dart b/lib/screens/home_page/collection_pane.dart index c44ed56e..abfc1f28 100644 --- a/lib/screens/home_page/collection_pane.dart +++ b/lib/screens/home_page/collection_pane.dart @@ -1,4 +1,3 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:apidash/providers/providers.dart'; @@ -67,33 +66,44 @@ class CollectionPane extends ConsumerWidget { style: kTextStyleButton, ), ), - ], ), ), kVSpacer10, - SizedBox( - height:30, - child:SearchBar( - - onChanged: (value){ - if(value.isNotEmpty){ - ref.read(searchQueryProvider.notifier).state = value; - ref.read(collectionStateNotifierProvider.notifier) - .filterRequests(value); - } - else{ - - } - - }, - hintText: "search", - leading: Icon(Icons.search), - textStyle: MaterialStateTextStyle.resolveWith((states) => TextStyle(fontSize: 15.0)), - elevation: MaterialStateProperty.all(2.0), - )), + Container( + height: 30, + margin: const EdgeInsets.only(right: 8), + decoration: BoxDecoration( + borderRadius: kBorderRadius8, + border: Border.all( + color: Theme.of(context).colorScheme.surfaceVariant, + ), + ), + child: Row( + children: [ + kHSpacer5, + Icon( + Icons.filter_alt, + size: 18, + color: Theme.of(context).colorScheme.secondary, + ), + kHSpacer5, + Expanded( + child: RawTextField( + style: Theme.of(context).textTheme.bodyMedium, + hintText: "Filter by name or URL", + onChanged: (value) { + if (value.trim().isNotEmpty) { + ref.read(searchQueryProvider.notifier).state = value; + print(value); + } + }, + ), + ), + ], + ), + ), kVSpacer10, - const Expanded( child: RequestList(), ), @@ -134,7 +144,7 @@ class _RequestListState extends ConsumerState { final alwaysShowCollectionPaneScrollbar = ref.watch(settingsProvider .select((value) => value.alwaysShowCollectionPaneScrollbar)); - return requestItems.isNotEmpty?Scrollbar( + return Scrollbar( controller: controller, thumbVisibility: alwaysShowCollectionPaneScrollbar ? true : null, radius: const Radius.circular(12), @@ -155,7 +165,6 @@ class _RequestListState extends ConsumerState { }, itemBuilder: (context, index) { var id = requestSequence[index]; - print(requestItems[id]!); return ReorderableDragStartListener( key: ValueKey(id), @@ -170,7 +179,7 @@ class _RequestListState extends ConsumerState { ); }, ), - ):Text("data"); + ); } } diff --git a/lib/widgets/textfields.dart b/lib/widgets/textfields.dart index da68f9e2..35d3437f 100644 --- a/lib/widgets/textfields.dart +++ b/lib/widgets/textfields.dart @@ -94,14 +94,39 @@ class JsonSearchField extends StatelessWidget { @override Widget build(BuildContext context) { - return TextField( + return RawTextField( controller: controller, onChanged: onChanged, style: kCodeStyle, - decoration: const InputDecoration( + hintText: 'Search..', + ); + } +} + +class RawTextField extends StatelessWidget { + const RawTextField({ + super.key, + this.onChanged, + this.controller, + this.hintText, + this.style, + }); + + final void Function(String)? onChanged; + final TextEditingController? controller; + final String? hintText; + final TextStyle? style; + + @override + Widget build(BuildContext context) { + return TextField( + controller: controller, + onChanged: onChanged, + style: style, + decoration: InputDecoration( isDense: true, border: InputBorder.none, - hintText: 'Search..', + hintText: hintText, ), ); } From 77cb9db9a35566a7c0b543026a3c092ca2a0be55 Mon Sep 17 00:00:00 2001 From: Ankit Mahato Date: Sun, 24 Mar 2024 01:20:38 +0530 Subject: [PATCH 3/7] Update collection_pane.dart --- lib/screens/home_page/collection_pane.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/screens/home_page/collection_pane.dart b/lib/screens/home_page/collection_pane.dart index abfc1f28..1ba202eb 100644 --- a/lib/screens/home_page/collection_pane.dart +++ b/lib/screens/home_page/collection_pane.dart @@ -165,7 +165,6 @@ class _RequestListState extends ConsumerState { }, itemBuilder: (context, index) { var id = requestSequence[index]; - return ReorderableDragStartListener( key: ValueKey(id), index: index, From 1451ea756702f58e4dc7b9aac2d1ca542dcc37c0 Mon Sep 17 00:00:00 2001 From: Ankit Mahato Date: Sun, 24 Mar 2024 02:37:47 +0530 Subject: [PATCH 4/7] Update collection_pane.dart --- lib/screens/home_page/collection_pane.dart | 68 +++++++++++----------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/lib/screens/home_page/collection_pane.dart b/lib/screens/home_page/collection_pane.dart index 1ba202eb..7d89a375 100644 --- a/lib/screens/home_page/collection_pane.dart +++ b/lib/screens/home_page/collection_pane.dart @@ -93,10 +93,7 @@ class CollectionPane extends ConsumerWidget { style: Theme.of(context).textTheme.bodyMedium, hintText: "Filter by name or URL", onChanged: (value) { - if (value.trim().isNotEmpty) { - ref.read(searchQueryProvider.notifier).state = value; - print(value); - } + ref.read(searchQueryProvider.notifier).state = value; }, ), ), @@ -143,41 +140,44 @@ class _RequestListState extends ConsumerState { final requestItems = ref.watch(collectionStateNotifierProvider)!; final alwaysShowCollectionPaneScrollbar = ref.watch(settingsProvider .select((value) => value.alwaysShowCollectionPaneScrollbar)); + final filterQuery = ref.watch(searchQueryProvider).trim(); return Scrollbar( controller: controller, thumbVisibility: alwaysShowCollectionPaneScrollbar ? true : null, radius: const Radius.circular(12), - child: ReorderableListView.builder( - padding: kPr8CollectionPane, - scrollController: controller, - buildDefaultDragHandles: false, - itemCount: requestItems.length, - onReorder: (int oldIndex, int newIndex) { - if (oldIndex < newIndex) { - newIndex -= 1; - } - if (oldIndex != newIndex) { - ref - .read(collectionStateNotifierProvider.notifier) - .reorder(oldIndex, newIndex); - } - }, - itemBuilder: (context, index) { - var id = requestSequence[index]; - return ReorderableDragStartListener( - key: ValueKey(id), - index: index, - child: Padding( - padding: kP1, - child: RequestItem( - id: id, - requestModel: requestItems[id]!, - ), - ), - ); - }, - ), + child: filterQuery.isEmpty + ? ReorderableListView.builder( + padding: kPr8CollectionPane, + scrollController: controller, + buildDefaultDragHandles: false, + itemCount: requestItems.length, + onReorder: (int oldIndex, int newIndex) { + if (oldIndex < newIndex) { + newIndex -= 1; + } + if (oldIndex != newIndex) { + ref + .read(collectionStateNotifierProvider.notifier) + .reorder(oldIndex, newIndex); + } + }, + itemBuilder: (context, index) { + var id = requestSequence[index]; + return ReorderableDragStartListener( + key: ValueKey(id), + index: index, + child: Padding( + padding: kP1, + child: RequestItem( + id: id, + requestModel: requestItems[id]!, + ), + ), + ); + }, + ) + : SizedBox(), ); } } From 4243904aa97d9fbb3d5a4d701312eb343bea9d66 Mon Sep 17 00:00:00 2001 From: Ankit Mahato Date: Sun, 24 Mar 2024 02:56:28 +0530 Subject: [PATCH 5/7] Filtered list --- lib/screens/home_page/collection_pane.dart | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/lib/screens/home_page/collection_pane.dart b/lib/screens/home_page/collection_pane.dart index d377e786..0ede2021 100644 --- a/lib/screens/home_page/collection_pane.dart +++ b/lib/screens/home_page/collection_pane.dart @@ -93,7 +93,8 @@ class CollectionPane extends ConsumerWidget { style: Theme.of(context).textTheme.bodyMedium, hintText: "Filter by name or URL", onChanged: (value) { - ref.read(searchQueryProvider.notifier).state = value; + ref.read(searchQueryProvider.notifier).state = + value.toLowerCase(); }, ), ), @@ -177,7 +178,24 @@ class _RequestListState extends ConsumerState { ); }, ) - : SizedBox(), + : ListView( + padding: kPe8, + controller: controller, + children: requestSequence.map((id) { + var item = requestItems[id]!; + if (item.url.toLowerCase().contains(filterQuery) || + item.name.toLowerCase().contains(filterQuery)) { + return Padding( + padding: kP1, + child: RequestItem( + id: id, + requestModel: item, + ), + ); + } + return const SizedBox(); + }).toList(), + ), ); } } From 8a8ded7d203fd00bb78374cfef6bea020278e113 Mon Sep 17 00:00:00 2001 From: Ankit Mahato Date: Sun, 24 Mar 2024 03:18:04 +0530 Subject: [PATCH 6/7] Update tests --- test/providers/ui_providers_test.dart | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/test/providers/ui_providers_test.dart b/test/providers/ui_providers_test.dart index dde4983f..9d34619d 100644 --- a/test/providers/ui_providers_test.dart +++ b/test/providers/ui_providers_test.dart @@ -214,13 +214,13 @@ void main() { }); group("Testing selectedIdEditStateProvider", () { - testWidgets( - 'selectedIdEditStateProvider should have an initial value of null', - (tester) async { + testWidgets('It should have an initial value of null', (tester) async { await tester.pumpWidget( const ProviderScope( child: MaterialApp( - home: CollectionPane(), + home: Scaffold( + body: CollectionPane(), + ), ), ), ); @@ -237,7 +237,9 @@ void main() { await tester.pumpWidget( const ProviderScope( child: MaterialApp( - home: CollectionPane(), + home: Scaffold( + body: CollectionPane(), + ), ), ), ); @@ -267,7 +269,9 @@ void main() { await tester.pumpWidget( const ProviderScope( child: MaterialApp( - home: CollectionPane(), + home: Scaffold( + body: CollectionPane(), + ), ), ), ); @@ -303,7 +307,9 @@ void main() { await tester.pumpWidget( const ProviderScope( child: MaterialApp( - home: CollectionPane(), + home: Scaffold( + body: CollectionPane(), + ), ), ), ); From f9f185ece97ca3e43d6ff2c2e7f513c7b84c5e50 Mon Sep 17 00:00:00 2001 From: Tanish2002 Date: Sun, 24 Mar 2024 20:11:44 +0530 Subject: [PATCH 7/7] fix: file picker on linux --- .../request_pane/request_form_data.dart | 5 +- lib/utils/file_utils.dart | 6 +- pubspec.lock | 80 ++++++++++++++++--- pubspec.yaml | 2 +- 4 files changed, 74 insertions(+), 19 deletions(-) diff --git a/lib/screens/home_page/editor_pane/details_card/request_pane/request_form_data.dart b/lib/screens/home_page/editor_pane/details_card/request_pane/request_form_data.dart index a6bafd6e..582b19ed 100644 --- a/lib/screens/home_page/editor_pane/details_card/request_pane/request_form_data.dart +++ b/lib/screens/home_page/editor_pane/details_card/request_pane/request_form_data.dart @@ -132,10 +132,9 @@ class _FormDataBodyState extends ConsumerState { onPressed: () async { var pickedResult = await pickFile(); if (pickedResult != null && - pickedResult.files.isNotEmpty && - pickedResult.files.first.path != null) { + pickedResult.path.isNotEmpty) { formRows[idx] = formRows[idx].copyWith( - value: pickedResult.files.first.path!, + value: pickedResult.path, ); setState(() {}); _onFieldChange(selectedId!); diff --git a/lib/utils/file_utils.dart b/lib/utils/file_utils.dart index 58d69513..8f7c6a72 100644 --- a/lib/utils/file_utils.dart +++ b/lib/utils/file_utils.dart @@ -1,10 +1,10 @@ import 'dart:io'; import 'dart:typed_data'; +import 'package:file_selector/file_selector.dart'; import 'package:path/path.dart' as p; import 'package:mime_dart/mime_dart.dart'; import 'package:uuid/uuid.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:file_picker/file_picker.dart'; const uuid = Uuid(); @@ -57,7 +57,7 @@ String getTempFileName() { return getNewUuid(); } -Future pickFile() async { - FilePickerResult? pickedResult = await FilePicker.platform.pickFiles(); +Future pickFile() async { + XFile? pickedResult = await openFile(); return pickedResult; } diff --git a/pubspec.lock b/pubspec.lock index a4e54dfb..3d7a7189 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -209,6 +209,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.7.2" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "55d7b444feb71301ef6b8838dbc1ae02e63dd48c8773f3810ff53bb1e2945b32" + url: "https://pub.dev" + source: hosted + version: "0.3.4+1" crypto: dependency: transitive description: @@ -289,14 +297,70 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" - file_picker: + file_selector: dependency: "direct main" description: - name: file_picker - sha256: caa6bc229eab3e32eb2f37b53a5f9d22a6981474afd210c512a7546c1e1a04f6 + name: file_selector + sha256: "5019692b593455127794d5718304ff1ae15447dea286cdda9f0db2a796a1b828" url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "1.0.3" + file_selector_android: + dependency: transitive + description: + name: file_selector_android + sha256: "1cd66575f063b689e041aec836905ba7be18d76c9f0634d0d75daec825f67095" + url: "https://pub.dev" + source: hosted + version: "0.5.0+7" + file_selector_ios: + dependency: transitive + description: + name: file_selector_ios + sha256: b015154e6d9fddbc4d08916794df170b44531798c8dd709a026df162d07ad81d + url: "https://pub.dev" + source: hosted + version: "0.5.1+8" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492" + url: "https://pub.dev" + source: hosted + version: "0.9.2+1" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: b15c3da8bd4908b9918111fa486903f5808e388b8d1c559949f584725a6594d6 + url: "https://pub.dev" + source: hosted + version: "0.9.3+3" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b + url: "https://pub.dev" + source: hosted + version: "2.6.2" + file_selector_web: + dependency: transitive + description: + name: file_selector_web + sha256: "619e431b224711a3869e30dbd7d516f5f5a4f04b265013a50912f39e1abc88c8" + url: "https://pub.dev" + source: hosted + version: "0.9.4+1" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: d3547240c20cabf205c7c7f01a50ecdbc413755814d6677f3cb366f04abcead0 + url: "https://pub.dev" + source: hosted + version: "0.9.3+1" fixnum: dependency: transitive description: @@ -382,14 +446,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.21" - flutter_plugin_android_lifecycle: - dependency: transitive - description: - name: flutter_plugin_android_lifecycle - sha256: b068ffc46f82a55844acfa4fdbb61fad72fa2aef0905548419d97f0f95c456da - url: "https://pub.dev" - source: hosted - version: "2.0.17" flutter_riverpod: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index b7c79bde..cfa20cb3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -49,13 +49,13 @@ dependencies: url: https://github.com/foss42/json_data_explorer.git ref: b7dde2f85dff4f482eed7eda4ef2a71344ef8b3a scrollable_positioned_list: ^0.3.8 - file_picker: ^6.2.0 flutter_svg: ^2.0.10+1 vector_graphics_compiler: ^1.1.9+1 code_builder: ^4.10.0 dart_style: ^2.3.6 json_text_field: ^1.1.0 csv: ^6.0.0 + file_selector: ^1.0.3 dependency_overrides: web: ^0.5.0