diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e4b143..7c65ce9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,23 +1,75 @@ -## 0.0.2 + +## 0.0.2 + + * Initial release. -## 0.0.3 + + +## 0.0.3 + + * Code Cleanup: Improved code efficiency by removing unused code and optimizing existing functionalities. + * Documentation: Added a practical example to the README to help new users get started more easily. -## 1.0.0 + + +## 1.0.0 + * Release -## 1.1.0 + + +## 1.1.0 + * Added `excludedCountryCodes` option to exclude specific countries from code execution -## 1.2.0 + + +## 1.2.0 + * Breaking Changes - Breaking: `borderBuilder` now accepts a `Widget` input instead of `Border` -* New Features - Added `excludedCountryCodes` from `PickCountryLookupService` -## 2.0.0 -* bump to follow Semantic Versioning \ No newline at end of file +Breaking: `borderBuilder` now accepts a `Widget` input instead of `Border` + +* New Features + +Added `excludedCountryCodes` from `PickCountryLookupService` + + + +## 2.0.0 + +* bump to follow Semantic Versioning + + + +## 3.0.0 + + +- Breaking Changes: + - Removed `searchField` property +- New Features: + - Added `customSearchField` property for complete search field override + - Enhanced `SearchFieldWidget` with extensive customization options: + - `searchPrefixIcon` + - `searchSuffixIcon` + - `searchTextStyle` + - `searchPlaceholderStyle` + - `searchDecoration` + - `searchBoxDecoration` + - `searchCursorColor` + - `searchBorderRadius` + - `searchContentPadding` + - `onSearchTap` + - `onSearchSubmitted` + - `searchAutofocus` + - `searchFocusNode` + - `searchBackgroundColor` + - `searchEnabled` +- Improvements: + - Significantly improved search field customization without requiring a complete override + - Enhanced flexibility in styling and behavior of th \ No newline at end of file diff --git a/README.md b/README.md index 1216604..a749e71 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ A highly customizable modal widget for Flutter that allows users to pick a count Add this to your package's pubspec.yaml file: ```dart dependencies: - pick_country_picker: ^2.0.0 + pick_country_picker: ^3.0.0 ``` 2. Install it You can install packages from the command line: @@ -72,7 +72,6 @@ CountryPickerModal( selectedCountryCode: 'US', title: 'Select Your Country', priorityCountryCodes: ['US', 'CA'], - searchField: TextField(decoration: InputDecoration(labelText: 'Search...')), countryListItemBuilder: (Country country) { return ListTile( title: Text(country.countryName), diff --git a/example/lib/analysis_options.yaml b/example/lib/analysis_options.yaml index 0d29021..86ebcb7 100644 --- a/example/lib/analysis_options.yaml +++ b/example/lib/analysis_options.yaml @@ -7,6 +7,9 @@ # The following line activates a set of recommended lints for Flutter apps, # packages, and plugins designed to encourage good coding practices. +analyzer: + errors: + library_private_types_in_public_api: ignore include: package:flutter_lints/flutter.yaml linter: diff --git a/example/lib/cupertino.dart b/example/lib/cupertino.dart index c23e774..8b3be65 100644 --- a/example/lib/cupertino.dart +++ b/example/lib/cupertino.dart @@ -1,12 +1,14 @@ import 'package:flutter/material.dart'; import 'package:pick_country_picker/pick_country_picker.dart'; -void main() => runApp(ExampleApp()); +void main() => runApp(const ExampleApp()); class ExampleApp extends StatelessWidget { + const ExampleApp({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { - return MaterialApp( + return const MaterialApp( debugShowCheckedModeBanner: false, title: 'Country Picker Example', home: HomePage(), @@ -15,6 +17,8 @@ class ExampleApp extends StatelessWidget { } class HomePage extends StatefulWidget { + const HomePage({Key? key}) : super(key: key); + @override _HomePageState createState() => _HomePageState(); } @@ -39,13 +43,13 @@ class _HomePageState extends State { hideSearch: false, selectedCountryIsoCode: selectedCountry?.iso2Code, title: 'Select your country', - priorityCountryCodes: ['US', 'CA', 'GB', 'LV'], + priorityCountryCodes: const ['US', 'CA', 'GB', 'LV'], onCountryChanged: (Country country) { setState(() => selectedCountry = country); Navigator.of(context).pop(); }, countryDisplayBuilder: (Country country) { - return '${country.countryName}'; + return country.countryName; }, flagBuilder: (Country country) { return Image.asset( @@ -65,7 +69,7 @@ class _HomePageState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text('Country Picker Example'), + title: const Text('Country Picker Example'), ), body: Center( child: Column( @@ -73,12 +77,12 @@ class _HomePageState extends State { children: [ ElevatedButton( onPressed: _showCountryPicker, - child: Text('Show Country Picker'), + child: const Text('Show Country Picker'), ), - SizedBox(height: 20), + const SizedBox(height: 20), if (selectedCountry != null) ...[ Text('Selected Country: ${selectedCountry!.countryName}'), - SizedBox(height: 10), + const SizedBox(height: 10), Image.asset( selectedCountry!.flagUri!, package: 'pick_country_picker', diff --git a/example/lib/main.dart b/example/lib/main.dart index 324940b..d8aac52 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -3,12 +3,14 @@ import 'package:pick_country_picker/pick_country_picker.dart'; // Define the constant for excluded country codes const List excludedCountryCodes = ["AX"]; -void main() => runApp(ExampleApp()); +void main() => runApp(const ExampleApp()); class ExampleApp extends StatelessWidget { + const ExampleApp({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { - return MaterialApp( + return const MaterialApp( debugShowCheckedModeBanner: false, title: 'Country Picker Example', home: HomePage(), @@ -17,19 +19,22 @@ class ExampleApp extends StatelessWidget { } class HomePage extends StatefulWidget { + const HomePage({Key? key}) : super(key: key); + @override _HomePageState createState() => _HomePageState(); } class _HomePageState extends State { Country? selectedCountry; + List filteredCountries = []; + final PickCountryLookupService _lookupService = + PickCountryLookupService(excludedCountryCodes: excludedCountryCodes); @override void initState() { super.initState(); - selectedCountry = - PickCountryLookupService(excludedCountryCodes: excludedCountryCodes) - .getCountryByCountryCode("358"); + selectedCountry = _lookupService.getCountryByCountryCode("358"); } void _showCountryPicker() { @@ -40,32 +45,20 @@ class _HomePageState extends State { height: MediaQuery.of(context).size.height * 0.90, child: CountryPickerModal( hideCloseIcon: true, - hideSearch: true, + hideSearch: false, backButton: Container(), selectedCountryIsoCode: selectedCountry?.iso2Code, excludedCountryCodes: excludedCountryCodes, title: 'Select your country', - searchField: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.search, color: Colors.grey[400]), - SizedBox(width: 8), - Text( - 'Search', - style: TextStyle( - color: Colors.grey[400], - fontSize: 16, - ), - ), - ], - ), - priorityCountryCodes: ['US', 'CA', 'GB', 'LV'], + + + priorityCountryCodes: const ['US', 'CA', 'GB', 'LV'], onCountryChanged: (Country country) { setState(() => selectedCountry = country); Navigator.of(context).pop(); }, countryDisplayBuilder: (Country country) { - return '${country.countryName}'; + return country.countryName; }, subtitleBuilder: (Country country) { return '+${country.countryCode}'; @@ -85,7 +78,6 @@ class _HomePageState extends State { width: 0.5, )), ), - useCupertinoModal: false, // Set to false to use Material design ), ), @@ -96,7 +88,7 @@ class _HomePageState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text('Country Picker Example'), + title: const Text('Country Picker Example'), ), body: Center( child: Column( @@ -104,12 +96,12 @@ class _HomePageState extends State { children: [ ElevatedButton( onPressed: _showCountryPicker, - child: Text('Show Country Picker'), + child: const Text('Show Country Picker'), ), - SizedBox(height: 20), + const SizedBox(height: 20), if (selectedCountry != null) ...[ Text('Selected Country: ${selectedCountry!.countryName}'), - SizedBox(height: 10), + const SizedBox(height: 10), Image.asset( selectedCountry!.flagUri!, package: 'pick_country_picker', diff --git a/example/lib/material.dart b/example/lib/material.dart index 05606d3..1afd5f9 100644 --- a/example/lib/material.dart +++ b/example/lib/material.dart @@ -1,12 +1,14 @@ import 'package:flutter/material.dart'; import 'package:pick_country_picker/pick_country_picker.dart'; -void main() => runApp(ExampleApp()); +void main() => runApp(const ExampleApp()); class ExampleApp extends StatelessWidget { + const ExampleApp({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { - return MaterialApp( + return const MaterialApp( debugShowCheckedModeBanner: false, title: 'Country Picker Example', home: HomePage(), @@ -15,6 +17,8 @@ class ExampleApp extends StatelessWidget { } class HomePage extends StatefulWidget { + const HomePage({Key? key}) : super(key: key); + @override _HomePageState createState() => _HomePageState(); } @@ -40,13 +44,13 @@ class _HomePageState extends State { backButton: Container(), selectedCountryIsoCode: selectedCountry?.iso2Code, title: 'Select your country', - priorityCountryCodes: ['US', 'CA', 'GB', 'LV'], + priorityCountryCodes: const ['US', 'CA', 'GB', 'LV'], onCountryChanged: (Country country) { setState(() => selectedCountry = country); Navigator.of(context).pop(); }, countryDisplayBuilder: (Country country) { - return '${country.countryName}'; + return country.countryName; }, subtitleBuilder: (Country country) { return '+${country.countryCode} (${country.iso2Code})'; @@ -69,7 +73,7 @@ class _HomePageState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text('Country Picker Example'), + title: const Text('Country Picker Example'), ), body: Center( child: Column( @@ -77,12 +81,12 @@ class _HomePageState extends State { children: [ ElevatedButton( onPressed: _showCountryPicker, - child: Text('Show Country Picker'), + child: const Text('Show Country Picker'), ), - SizedBox(height: 20), + const SizedBox(height: 20), if (selectedCountry != null) ...[ Text('Selected Country: ${selectedCountry!.countryName}'), - SizedBox(height: 10), + const SizedBox(height: 10), Image.asset( selectedCountry!.flagUri!, package: 'pick_country_picker', diff --git a/example/lib/pubspec.lock b/example/lib/pubspec.lock index 77f470b..f3ff413 100644 --- a/example/lib/pubspec.lock +++ b/example/lib/pubspec.lock @@ -76,34 +76,34 @@ packages: dependency: transitive description: name: intl - sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf url: "https://pub.dev" source: hosted - version: "0.18.1" + version: "0.19.0" leak_tracker: dependency: transitive description: name: leak_tracker - sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.0" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.1" matcher: dependency: transitive description: @@ -116,18 +116,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.15.0" path: dependency: transitive description: @@ -142,7 +142,7 @@ packages: path: "../.." relative: true source: path - version: "1.0.0" + version: "2.0.0" sky_engine: dependency: transitive description: flutter @@ -192,10 +192,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.7.2" vector_math: dependency: transitive description: @@ -208,10 +208,10 @@ packages: dependency: transitive description: name: vm_service - sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc url: "https://pub.dev" source: hosted - version: "13.0.0" + version: "14.2.4" sdks: - dart: ">=3.2.0-0 <4.0.0" - flutter: ">=1.20.0" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/example/lib/test/widget_test.dart b/example/lib/test/widget_test.dart index 34990e9..f467040 100644 --- a/example/lib/test/widget_test.dart +++ b/example/lib/test/widget_test.dart @@ -8,7 +8,7 @@ void main() { testWidgets('ExampleApp and CountryPickerModal display correctly', (WidgetTester tester) async { // Build our app and trigger a frame. - await tester.pumpWidget(ExampleApp()); + await tester.pumpWidget(const ExampleApp()); // Verify that the app is displayed correctly. expect(find.byType(ExampleApp), findsOneWidget); diff --git a/lib/src/widgets/country_picker_modal.dart b/lib/src/widgets/country_picker_modal.dart index 9e5c6b7..8e37955 100644 --- a/lib/src/widgets/country_picker_modal.dart +++ b/lib/src/widgets/country_picker_modal.dart @@ -14,7 +14,7 @@ class CountryPickerModal extends StatefulWidget { /// Passes the newly selected [Country] object to the caller for further processing. final Function(Country) onCountryChanged; - /// An optional ISO 3166-1 alpha-2 code or country code to pre-select a country. + /// An optional [Country] object to pre-select a country. /// If provided, the specified country will be highlighted and placed at the top of the list. final Country? selectedCountry; @@ -27,7 +27,7 @@ class CountryPickerModal extends StatefulWidget { final String? selectedCountryName; /// Optional parameter to specify the country to be pre-selected using its ISO 3166-1 alpha-2 - /// ISO code (e.g., 'US', 'CA'). + /// ISO code (e.g., 'US', 'CA'). final String? selectedCountryIsoCode; /// Custom title for the modal. Useful for localization or when a different title is preferred @@ -42,8 +42,8 @@ class CountryPickerModal extends StatefulWidget { /// Use this to restrict the selection to a specific set of countries, overriding the default list. final List? overrideCountryCodes; - /// Country ISO codes to exclusively remove from a picker. - /// Use this to restrict the selection to a specific set of countries, overriding the default list. + /// Country ISO codes to exclusively remove from the picker. + /// Use this to exclude specific countries from the selection list. final List? excludedCountryCodes; /// Controls whether to display the search field. If true, users can only scroll to find countries. @@ -58,7 +58,7 @@ class CountryPickerModal extends StatefulWidget { /// Provides a completely custom search field widget. /// Use this to tailor the search field's appearance and behavior. - final Widget? searchField; + final Widget? customSearchField; /// Custom widget for the back or close button in the modal's app bar. /// Provides a way to customize the appearance or behavior of the back navigation. @@ -94,7 +94,52 @@ class CountryPickerModal extends StatefulWidget { /// for each ListTile. final Widget? borderBuilder; - CountryPickerModal({ + /// Custom prefix icon for the search field. + final Widget? searchPrefixIcon; + + /// Custom suffix icon for the search field. + final Icon? searchSuffixIcon; + + /// Custom text style for the search field input. + final TextStyle? searchTextStyle; + + /// Custom text style for the search field placeholder text. + final TextStyle? searchPlaceholderStyle; + + /// Custom input decoration for the search field (Material design only). + final InputDecoration? searchDecoration; + + /// Custom box decoration for the search field (Cupertino design only). + final BoxDecoration? searchBoxDecoration; + + /// Custom cursor color for the search field. + final Color? searchCursorColor; + + /// Custom border radius for the search field. + final double? searchBorderRadius; + + /// Custom content padding for the search field. + final EdgeInsetsGeometry? searchContentPadding; + + /// Callback function triggered when the search field is tapped. + final Function()? onSearchTap; + + /// Callback function triggered when the search is submitted. + final Function(String)? onSearchSubmitted; + + /// Controls whether the search field should be auto-focused when the modal is opened. + final bool searchAutofocus; + + /// Custom focus node for the search field. + final FocusNode? searchFocusNode; + + /// Custom background color for the search field. + final Color? searchBackgroundColor; + + /// Controls whether the search field is enabled or disabled. + final bool? searchEnabled; + + const CountryPickerModal({ super.key, required this.onCountryChanged, this.selectedCountry, @@ -108,7 +153,7 @@ class CountryPickerModal extends StatefulWidget { this.hideSearch = false, this.hideCloseIcon = false, this.useCupertinoModal = false, - this.searchField, + this.customSearchField, this.backButton, this.countryListItemBuilder, this.selectedIcon, @@ -119,19 +164,36 @@ class CountryPickerModal extends StatefulWidget { this.cancelText = 'Cancel', this.placeholderText = 'Search for a country', this.borderBuilder, - }) : assert( - overrideCountryCodes == null || - excludedCountryCodes == null || - overrideCountryCodes - .toSet() - .intersection(excludedCountryCodes.toSet()) - .isEmpty, - 'overrideCountryCodes and excludedCountryCodes must not contain any of the same values.', - ); - + this.searchPrefixIcon, + this.searchSuffixIcon, + this.searchTextStyle, + this.searchPlaceholderStyle, + this.searchDecoration, + this.searchBoxDecoration, + this.searchCursorColor, + this.searchBorderRadius, + this.searchContentPadding, + this.onSearchTap, + this.onSearchSubmitted, + this.searchAutofocus = false, + this.searchFocusNode, + this.searchBackgroundColor, + this.searchEnabled, + }); @override CountryPickerModalState createState() => CountryPickerModalState(); + + /// Validates that overrideCountryCodes and excludedCountryCodes don't contain overlapping values. + bool validateCountryCodes() { + if (overrideCountryCodes == null || excludedCountryCodes == null) { + return true; + } + return overrideCountryCodes! + .toSet() + .intersection(excludedCountryCodes!.toSet()) + .isEmpty; + } } class CountryPickerModalState extends State { @@ -139,7 +201,6 @@ class CountryPickerModalState extends State { List _countries = []; List _filteredCountries = []; Country? _selectedCountry; - Country? initialCountry; @override void initState() { @@ -157,7 +218,6 @@ class CountryPickerModalState extends State { excludedCountryCodes: widget.excludedCountryCodes, ); - // Determine the initial country based on the provided properties if (_selectedCountry == null) { if (widget.selectedCountryCode != null) { _selectedCountry = pickCountryLookupService @@ -173,7 +233,6 @@ class CountryPickerModalState extends State { } } - // If an initial country is determined, place it at the top of the list if (_selectedCountry != null) { _countries.removeWhere( (country) => country.iso2Code == _selectedCountry!.iso2Code); @@ -194,26 +253,52 @@ class CountryPickerModalState extends State { Widget _buildCountryPickerList() { return CountryListWidget( - availableCountries: _filteredCountries, - selectedCountry: _selectedCountry, - onCountrySelected: widget.onCountryChanged, - borderBuilder: widget.borderBuilder, - flagBuilder: widget.flagBuilder, - selectedIcon: widget.selectedIcon, - countryDisplayBuilder: widget.countryDisplayBuilder, - subtitleBuilder: widget.subtitleBuilder); + availableCountries: _filteredCountries, + selectedCountry: _selectedCountry, + onCountrySelected: (country) { + setState(() { + _selectedCountry = country; + }); + widget.onCountryChanged(country); + }, + borderBuilder: widget.borderBuilder, + flagBuilder: widget.flagBuilder, + selectedIcon: widget.selectedIcon, + countryDisplayBuilder: widget.countryDisplayBuilder, + subtitleBuilder: widget.subtitleBuilder, + ); } Widget _buildSearchField() { - return widget.hideSearch - ? const SizedBox.shrink() - : widget.searchField ?? - SearchFieldWidget( - controller: _searchController, - onSearchChanged: _filterCountries, - useCupertino: widget.useCupertinoModal, - placeholder: widget.placeholderText, - ); + if (widget.hideSearch) { + return const SizedBox.shrink(); + } + + if (widget.customSearchField != null) { + return widget.customSearchField!; + } + + return SearchFieldWidget( + controller: _searchController, + onSearchChanged: _filterCountries, + useCupertino: widget.useCupertinoModal, + placeholder: widget.placeholderText, + prefixIcon: widget.searchPrefixIcon, + suffixIcon: widget.searchSuffixIcon, + textStyle: widget.searchTextStyle, + placeholderStyle: widget.searchPlaceholderStyle, + decoration: widget.searchDecoration, + boxDecoration: widget.searchBoxDecoration, + cursorColor: widget.searchCursorColor, + borderRadius: widget.searchBorderRadius, + contentPadding: widget.searchContentPadding, + onTap: widget.onSearchTap, + onSubmitted: widget.onSearchSubmitted, + autofocus: widget.searchAutofocus, + focusNode: widget.searchFocusNode, + backgroundColor: widget.searchBackgroundColor, + enabled: widget.searchEnabled, + ); } @override @@ -227,29 +312,19 @@ class CountryPickerModalState extends State { if (widget.useCupertinoModal) { return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: Text(widget.title), + leading: widget.hideCloseIcon + ? null + : (widget.backButton ?? + CupertinoButton( + padding: EdgeInsets.zero, + child: Text(widget.cancelText), + onPressed: () => Navigator.of(context).pop(), + )), + ), child: SafeArea( - child: Column( - children: [ - Padding( - padding: - const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded(child: _buildSearchField()), - widget.backButton ?? - CupertinoButton( - child: Text(widget.cancelText), - onPressed: () => Navigator.of(context).pop(), - ), - ], - ), - ), - Expanded( - child: _buildCountryPickerList(), - ), - ], - ), + child: modalContent, ), ); } else { @@ -260,8 +335,17 @@ class CountryPickerModalState extends State { ? null : (widget.backButton ?? const BackButton()), ), - body: modalContent, + body: Padding( + padding: const EdgeInsets.all(16.0), + child: modalContent, + ), ); } } + + @override + void dispose() { + _searchController.dispose(); + super.dispose(); + } } diff --git a/lib/src/widgets/search_field_widget.dart b/lib/src/widgets/search_field_widget.dart index 1305068..a8b9623 100644 --- a/lib/src/widgets/search_field_widget.dart +++ b/lib/src/widgets/search_field_widget.dart @@ -1,37 +1,142 @@ import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; +/// A highly customizable search widget that supports both Material and Cupertino designs. class SearchFieldWidget extends StatelessWidget { + /// Controller for the search text field. final TextEditingController controller; + + /// Callback function triggered when the search text changes. final Function(String) onSearchChanged; + + /// Determines whether to use Cupertino-style widgets (iOS) or Material-style widgets (Android). final bool useCupertino; + + /// Placeholder text displayed when the search field is empty. final String placeholder; + /// Widget to be displayed as a prefix icon in the search field. + final Widget? prefixIcon; + + /// Icon to be displayed as a suffix icon in the search field. + final Icon? suffixIcon; + + /// Text style for the input text in the search field. + final TextStyle? textStyle; + + /// Text style for the placeholder text in the search field. + final TextStyle? placeholderStyle; + + /// Decoration for the Material-style TextField. + final InputDecoration? decoration; + + /// Decoration for the Cupertino-style search field container. + final BoxDecoration? boxDecoration; + + /// Color of the text input cursor. + final Color? cursorColor; + + /// Border radius for the search field container. + final double? borderRadius; + + /// Padding inside the search field. + final EdgeInsetsGeometry? contentPadding; + + /// Callback function triggered when the search field is tapped. + final Function()? onTap; + + /// Callback function triggered when the search is submitted. + final Function(String)? onSubmitted; + + /// Determines if the search field should be auto-focused when displayed. + final bool autofocus; + + /// FocusNode for controlling the focus of the search field. + final FocusNode? focusNode; + + /// Background color of the search field. + final Color? backgroundColor; + + /// Determines if the search field is enabled or disabled. + final bool? enabled; + const SearchFieldWidget({ super.key, required this.controller, required this.onSearchChanged, this.useCupertino = false, this.placeholder = 'Search', + this.prefixIcon, + this.suffixIcon, + this.textStyle, + this.placeholderStyle, + this.decoration, + this.boxDecoration, + this.cursorColor, + this.borderRadius, + this.contentPadding, + this.onTap, + this.onSubmitted, + this.autofocus = false, + this.focusNode, + this.backgroundColor, + this.enabled, }); @override Widget build(BuildContext context) { - return useCupertino - ? CupertinoSearchTextField( - controller: controller, - onChanged: onSearchChanged, - placeholder: placeholder, - ) - : TextField( - controller: controller, - onChanged: onSearchChanged, - decoration: InputDecoration( - labelText: placeholder, - prefixIcon: const Icon(Icons.search), - contentPadding: - const EdgeInsets.symmetric(vertical: 0.0, horizontal: 16.0), + if (useCupertino) { + return CupertinoSearchTextField( + controller: controller, + onChanged: onSearchChanged, + placeholder: placeholder, + placeholderStyle: placeholderStyle, + style: textStyle, + decoration: boxDecoration, + prefixIcon: prefixIcon ?? const SizedBox.shrink(), + suffixIcon: suffixIcon ?? const Icon(CupertinoIcons.xmark_circle_fill), + onTap: onTap, + onSubmitted: onSubmitted, + autofocus: autofocus, + focusNode: focusNode, + backgroundColor: backgroundColor ?? CupertinoColors.systemGrey6, + enabled: enabled ?? true, + ); + } else { + return TextField( + controller: controller, + onChanged: onSearchChanged, + style: textStyle, + cursorColor: cursorColor, + decoration: decoration ?? + InputDecoration( + hintText: placeholder, + hintStyle: placeholderStyle, + prefixIcon: prefixIcon ?? const Icon(Icons.search), + suffixIcon: suffixIcon, + filled: backgroundColor != null, + fillColor: backgroundColor, + contentPadding: contentPadding ?? + const EdgeInsets.symmetric(vertical: 10, horizontal: 12), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(borderRadius ?? 8), + borderSide: BorderSide.none, + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(borderRadius ?? 8), + borderSide: BorderSide.none, + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(borderRadius ?? 8), + borderSide: BorderSide(color: Theme.of(context).primaryColor), + ), ), - ); + onTap: onTap, + onSubmitted: onSubmitted, + autofocus: autofocus, + focusNode: focusNode, + enabled: enabled, + ); + } } } diff --git a/pubspec.lock b/pubspec.lock index b9d3fc7..fd04f43 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -84,34 +84,34 @@ packages: dependency: transitive description: name: intl - sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf url: "https://pub.dev" source: hosted - version: "0.18.1" + version: "0.19.0" leak_tracker: dependency: transitive description: name: leak_tracker - sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.0" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.1" lints: dependency: transitive description: @@ -132,18 +132,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.15.0" path: dependency: transitive description: @@ -201,10 +201,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.7.2" vector_math: dependency: transitive description: @@ -217,10 +217,10 @@ packages: dependency: transitive description: name: vm_service - sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc url: "https://pub.dev" source: hosted - version: "13.0.0" + version: "14.2.4" sdks: - dart: ">=3.2.0-0 <4.0.0" - flutter: ">=1.20.0" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/pubspec.yaml b/pubspec.yaml index 28fbd7d..9395a00 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pick_country_picker description: A highly customizable Flutter package for selecting a country from a modal list. -version: 2.0.0 +version: 3.0.0 homepage: repository: https://github.com/ThickLine/pick_country_picker @@ -11,7 +11,7 @@ environment: dependencies: flutter: sdk: flutter - collection: ^1.15.0 + collection: ^1.18.0 cupertino_icons: ^1.0.8 flutter_localizations: sdk: flutter