Repository: GAM3RG33K/flutter_settings_screens Branch: master Commit: 18f937739d7f Files: 86 Total size: 279.2 KB Directory structure: gitextract_u3mrbwrx/ ├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── example/ │ ├── .gitignore │ ├── .metadata │ ├── README.md │ ├── android/ │ │ ├── .gitignore │ │ ├── app/ │ │ │ ├── build.gradle │ │ │ └── src/ │ │ │ ├── debug/ │ │ │ │ └── AndroidManifest.xml │ │ │ ├── main/ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ ├── java/ │ │ │ │ │ └── com/ │ │ │ │ │ └── happyworks/ │ │ │ │ │ └── flutter_settings_screens_example/ │ │ │ │ │ └── MainActivity.java │ │ │ │ └── res/ │ │ │ │ ├── drawable/ │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable-v21/ │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── values/ │ │ │ │ │ └── styles.xml │ │ │ │ └── values-night/ │ │ │ │ └── styles.xml │ │ │ └── profile/ │ │ │ └── AndroidManifest.xml │ │ ├── build.gradle │ │ ├── gradle/ │ │ │ └── wrapper/ │ │ │ └── gradle-wrapper.properties │ │ ├── gradle.properties │ │ └── settings.gradle │ ├── ios/ │ │ ├── .gitignore │ │ ├── Flutter/ │ │ │ ├── AppFrameworkInfo.plist │ │ │ ├── Debug.xcconfig │ │ │ └── Release.xcconfig │ │ ├── Podfile │ │ ├── Runner/ │ │ │ ├── AppDelegate.swift │ │ │ ├── Assets.xcassets/ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── LaunchImage.imageset/ │ │ │ │ ├── Contents.json │ │ │ │ └── README.md │ │ │ ├── Base.lproj/ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ └── Main.storyboard │ │ │ ├── Info.plist │ │ │ └── Runner-Bridging-Header.h │ │ ├── Runner.xcodeproj/ │ │ │ ├── project.pbxproj │ │ │ ├── project.xcworkspace/ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ └── xcshareddata/ │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ │ └── xcshareddata/ │ │ │ └── xcschemes/ │ │ │ └── Runner.xcscheme │ │ └── Runner.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings │ ├── lib/ │ │ ├── app_settings_page.dart │ │ ├── cache_provider.dart │ │ └── main.dart │ ├── macos/ │ │ ├── .gitignore │ │ ├── Flutter/ │ │ │ ├── Flutter-Debug.xcconfig │ │ │ ├── Flutter-Release.xcconfig │ │ │ └── GeneratedPluginRegistrant.swift │ │ ├── Podfile │ │ ├── Runner/ │ │ │ ├── AppDelegate.swift │ │ │ ├── Assets.xcassets/ │ │ │ │ └── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ ├── Base.lproj/ │ │ │ │ └── MainMenu.xib │ │ │ ├── Configs/ │ │ │ │ ├── AppInfo.xcconfig │ │ │ │ ├── Debug.xcconfig │ │ │ │ ├── Release.xcconfig │ │ │ │ └── Warnings.xcconfig │ │ │ ├── DebugProfile.entitlements │ │ │ ├── Info.plist │ │ │ ├── MainFlutterWindow.swift │ │ │ └── Release.entitlements │ │ ├── Runner.xcodeproj/ │ │ │ ├── project.pbxproj │ │ │ ├── project.xcworkspace/ │ │ │ │ └── xcshareddata/ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ └── xcshareddata/ │ │ │ └── xcschemes/ │ │ │ └── Runner.xcscheme │ │ └── Runner.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ └── IDEWorkspaceChecks.plist │ ├── pubspec.yaml │ ├── test/ │ │ └── widget_test.dart │ └── web/ │ ├── index.html │ └── manifest.json ├── lib/ │ ├── flutter_settings_screens.dart │ └── src/ │ ├── cache/ │ │ ├── cache.dart │ │ ├── cache_provider.dart │ │ └── cache_provider_impl.dart │ ├── settings.dart │ ├── utils/ │ │ ├── utils.dart │ │ └── widget_utils.dart │ └── widgets/ │ ├── base_widgets.dart │ ├── color_picker/ │ │ ├── circle_color.dart │ │ ├── colors.dart │ │ └── material_color_picker.dart │ └── settings_widgets.dart ├── pubspec.yaml └── test/ └── flutter_settings_screens_test.dart ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ # IntelliJ related *.iml *.ipr *.iws .idea/ .idea/* # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related **/doc/api/ .dart_tool/ .flutter-plugins .packages .pub-cache/ .pub/ /build/ # Web related lib/generated_plugin_registrant.dart # Exceptions to above rules. !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages pubspec.lock example/.flutter-plugins-dependencies example/pubspec.lock .flutter-plugins-dependencies /.fvm/ ================================================ FILE: .metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: 2c7af1e24e45a79f4eb73d67d98fcecea8bf6146 channel: master project_type: plugin ================================================ FILE: CHANGELOG.md ================================================ ## 0.3.4 - Finalised release of 0.3.3-null-safety+2 with Null-safety, Material 3 & flutter 3 changes - Huge thanks to @nyxkn for all fixes & updates for this release - Huge thanks to @romanbsd for all updates to Flutter 3 changes ## 0.3.3-null-safety+2 - add flag to enable textfield content to be autoselected on focus - change capitalization of autovalidateMode - TextInput: expose helperText and inputFormatters - fix: null-safety warning messages - fix: valuechangeobserver dispose accidentally clears the whole _notifier map rather than just its own cachekey - Huge thanks to @nyxkn for all fixes & updates for this release ## 0.3.3-null-safety+1 - Fix issue where implementation for setObject in default cache provider always throws exception - Custom text style in all widgets - Clear the notifiers map when the widget is disposed ## 0.3.3-null-safety - Breaking Changes: - Cache Provider interface definition updated to allow asynchronous getter/setter - Optional Default values are moved to getters instead of setters - Flutter SDK version upgraded to 2.15.0 & upgraded the plugin dependencies - Changed all getters & setters definitions to support null values - Changed Settings implementation according to new changes - Example app moved Flutter SDK to 2.15.0 - Updated example app implementation - Updated Code documentation in cache provider interface ## 0.3.2-null-safety - Provider version updated to 6.0.0, thanks to @Pawelek55 - ExpandedSettingTile were not using the provided expanded status, fixed by @Colton127 - Updated theme changes in the source code & in the example to resolve analyzer warnings ## 0.3.1-null-safety - added functionality to update the specific SettingsUI by updating it's value by calling setValue with `notify` as `true`. Example: ```dart await Settings.setValue(cacheKey, newValue, notify: true); ``` - Fix - Unresponsive tapping of the switch tile, fixed by @pascalwils - Updated ReadMe content to match new updates - Updated example app code to demo the UI update functionality - Checkout Auto adjusting Volume slider ## 0.3.0-null-safety - null-safety migration - bug-fix for slider not respecting step value in decimal points ## 0.2.2+1 - complete dependency update to latest ## 0.2.2 - remove autovalidate option to comply with new sdk changes - remove unnecessary use of Generic cache provider interface `Set getKeys()` -> `Set getKeys()` - reason for this is that generics may restrict some implementation in some way - to achieve the same effect as the generics implementation, one can just `cast` the set as they want - if the whole interface depends on generics only then the previous declaration makes sense. - update & fix example app code - plugin code organization & documentation updates #### Developer Note: 1. The sdk upgrade will be done in two stages - stage 1: only update the dependencies with code changes to comply with the updates - stage 2: update the flutter/dart sdk version along with the least version of supported dependencies 2. Few of the next releases may contain some breaking changes in relation to cache provider implementation 3. A few of the planned updates: - null safety support for library - cache provider structuring to support universal implementation, allows using any storage platform to be used like, shared_preferences, hive, flutter_secure_storage, etc - massive UI customization in terms of platforms & designs - conditional changes or changes with confirmation - settings value change observation **If you have any suggestions and/or support to offer please file an issue in the repository & let me know, use `[Suggestion]` or `[FeatureRequest]` tags in issue titles** ## 0.2.1+1 * improved overall alignment of settings tiles * update cache provide code to make asynchronous calls to setter methods - autoValidated parameter in text input settings is now deprecated and will be removed soon. User `autoValidateMode` parameter instead. * removed native platform dependency code as this library does not depend on native features. At least not directly. ## 0.2.1 * `SimpleSettingsTile` will take any widget as `child` instead of only `SettingsScreen` - **Breaking**: parameter name changed from `screen` to `child` for consistency * Added `subtitle` property for most settings tiles to allow a describing how this setting may introduce change in behaviour of the app * Improved Settings title and subtitle text style for consistency in UI * `SliderSettings` now have 2 additional callbacks: - onChangeStart - allows detecting drag start event - onChangeEnd - allows detecting drag end event - Using this allows changing the slider value only when user stops sliding * Updated Example code to reflect latest features ## 0.2.0+1 * improved plugin initialization, now supports async method call * resolved a bug where Radio Settings was not reflecting changes ## 0.2.0 * complete re-do of the whole library * removed rx-dart dependency * improved the working of the many existing settings widgets * many of the choice based widgets now support any primitive value as input/output values instead of just strings * added more customization choices per setting widget * added a default cache provider which is based on `shared_preferences` library by flutter team * updated code documentation #### Breaking Change: Your existing use of some settings widgets might show error or not work as due to them being re-designed, like change in name/type of the parameters or the widget itself is renamed. This was a major re-design/refactor of the library, so please re-test part of your code which uses this library. ## 0.1.0+0.2 * update in license file ## 0.1.0+0.1 * 0.1.0 release + update in documentation and sdk version constraints ## 0.1.0 * first release ## 0.0.1 * initial code release ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2019 Harshvardhan Joshi Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # flutter_settings_screens [![pub package](https://img.shields.io/pub/v/flutter_settings_screens.svg)](https://pub.dev/packages/flutter_settings_screens) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) This is a simple flutter plugin for easily creating app settings screens. The unique thing about this library is that it is not dependent upon any specific storage library used to store settings. Inspired by the [shared_preferences_settings](https://pub.dev/packages/shared_preferences_settings) plugin. **Update: Now with Null-safety & Material3 support** ## Features - A collection of settings widgets to make a settings page in a few seconds and get going. - **Normal**: - SimpleSettingsTile - Switch/Toggle setting - Checkbox setting - Drop down setting - Radio selection Setting - Slider setting - Color choice panel - Text Input Setting - **Advanced**: - SettingsScreen: > A Flutter Widget/Page which can contain all settings widget. - ExpandableSettingsTile > A settings widget which can hold a set of widgets in a section which is collapsible - SettingsContainer > A Settings widget that helps any flutter widget fit into the settings page - SettingsGroup > A Container widget that creates a section with a title to separate settings inside this from other settings - Settings saved via "CacheProvider" library of your choice - default version uses SharedPreferences. - Widgets with conditional visibility of some other settings. - for example, A set of settings is only visible if a switch or a checkbox is enabled. ## Examples ![](https://github.com/GAM3RG33K/flutter_settings_screens/blob/master/media/example_1.gif?raw=true "") ![](https://github.com/GAM3RG33K/flutter_settings_screens/blob/master/media/example_2.gif?raw=true "") ![](https://github.com/GAM3RG33K/flutter_settings_screens/blob/master/media/example_3.gif?raw=true "") ![](https://github.com/GAM3RG33K/flutter_settings_screens/blob/master/media/example_4.gif?raw=true "") ![](https://github.com/GAM3RG33K/flutter_settings_screens/blob/master/media/example_5.gif?raw=true "") ## Initializing the plugin Initialize the plugin as following: ```dart await Settings.init(cacheProvider: _customCacheProvider); ``` **Note**: The plugin must be initialized before Navigating to the settings page. It is recommended that `Settings.init()` should be called before `runApp()` is called in the main file. However, anywhere before showing the settings page is fine. ### Cache Provider Interface Cache Provider is an interface by which the plugin accesses the underlying caching storage. This plugin includes an implementation of the `CacheProvider` using the `SharedPreferences` library by flutter. If `cacheProvider` parameter is not given explicitly then the default implementation will be used to store the settings. However, if you wish to use other means for storing the data, you can implement one by yourself. All you have to do is create a class as follows: ```dart import 'package:flutter_settings_screens/flutter_settings_screens.dart'; class CustomCacheProvider extends CacheProvider { ///... ///implement the methods as you want ///... } ``` for example, ```dart /// A cache access provider class for shared preferences using shared_preferences library class SharePreferenceCache extends CacheProvider { //... } ``` OR ```dart /// A cache access provider class for shared preferences using Hive library class HiveCache extends CacheProvider { //... } ``` Once you implement the class, use an instance of this class to initialize the Settings class. ## Accessing/Retrieving data You can use static methods of `Settings` class to access any data from the storage. Get value: ```dart Settings.getValue(cacheKey, defaultValue); ``` Set value(no UI updates): ```dart await Settings.setValue(cacheKey, newValue); ``` Set value(with UI updates): ```dart await Settings.setValue(cacheKey, newValue, notify: true); ``` T represents any of the following: - String - bool - int - double - Object For example if you want to access a String value from the storage: Get value: ```dart Settings.getValue(cacheKey, defaultValue); ``` Set value: ```dart await Settings.setValue(cacheKey, newValue, notify: true); ``` ### Special-Note: Since, `Color` or `MaterialColor` is a Flutter object, we need to convert it to string version of the color before saving it to cache & convert string version to color while fetching it from the cache. For that the plugin exposes `ConversionUtils` class with utility method to do that needed. From color to string: ```dart String colorString = ConversionUtils.stringFromColor(Colors.blue); ``` From string to color: ```dart Color color = ConversionUtils.colorFromString('#0000ff'); ``` ## Tile widgets #### SimpleSettingsTile SimpleSettingsTile is a simple settings tile that can open a new screen by tapping the tile. Example: ```dart SimpleSettingsTile( title: 'Advanced', subtitle: 'More, advanced settings.' screen: SettingsScreen( title: 'Sub menu', children: [ CheckboxSettingsTile( settingsKey: 'key-of-your-setting', title: 'This is a simple Checkbox', ), ], ), ); ``` #### SettingsTileGroup SettingsGroup is a widget that contains multiple settings tiles and other widgets together as a group and shows a title/name of that group. All the children widget will have small padding from the left and top to provide a sense that they in a separate group from others Example: ```dart SettingsGroup( title: 'Group title', children: [ CheckboxSettingsTile( settingKey: 'key-day-light-savings', title: 'Daylight Time Saving', enabledLabel: 'Enabled', disabledLabel: 'Disabled', leading: Icon(Icons.timelapse), ), SwitchSettingsTile( settingKey: 'key-dark-mode', title: 'Dark Mode', enabledLabel: 'Enabled', disabledLabel: 'Disabled', leading: Icon(Icons.palette), ), ], ); ``` #### ExpandableSettingsTile ExpandableSettingsTile is a wrapper widget that shows the given children when expanded by clicking on the tile. Example: ```dart ExpandableSettingsTile( title: 'Quick setting dialog2', subtitle: 'Expandable Settings', children: [ CheckboxSettingsTile( settingKey: 'key-day-light-savings', title: 'Daylight Time Saving', enabledLabel: 'Enabled', disabledLabel: 'Disabled', leading: Icon(Icons.timelapse), ), SwitchSettingsTile( settingKey: 'key-dark-mode', title: 'Dark Mode', enabledLabel: 'Enabled', disabledLabel: 'Disabled', leading: Icon(Icons.palette), ), ], ); ``` #### CheckboxSettingsTile CheckboxSettingsTile is a widget that has a Checkbox with given title, subtitle and default value/status of the Checkbox This widget supports an additional list of widgets to display when the Checkbox is checked. This optional list of widgets is accessed through `childrenIfEnabled` property of this widget. This widget works similar to `SwitchSettingsTile`. Example: ```dart CheckboxSettingsTile( leading: Icon(Icons.developer_mode), settingKey: 'key-check-box-dev-mode', title: 'Developer Settings', onChange: (value) { debugPrint('key-check-box-dev-mode: $value'); }, childrenIfEnabled: [ CheckboxSettingsTile( leading: Icon(Icons.adb), settingKey: 'key-is-developer', title: 'Developer Mode', onChange: (value) { debugPrint('key-is-developer: $value'); }, ), SwitchSettingsTile( leading: Icon(Icons.usb), settingKey: 'key-is-usb-debugging', title: 'USB Debugging', onChange: (value) { debugPrint('key-is-usb-debugging: $value'); }, ), ], ); ``` #### SwitchSettingsTile SwitchSettingsTile is a widget that has a Switch with given title, subtitle and default value/status of the switch This widget supports an additional list of widgets to display when the switch is enabled. This optional list of widgets is accessed through `childrenIfEnabled` property of this widget. This widget works similar to `CheckboxSettingsTile`. Example: ```dart SwitchSettingsTile( leading: Icon(Icons.developer_mode), settingKey: 'key-switch-dev-mode', title: 'Developer Settings', onChange: (value) { debugPrint('key-switch-dev-mod: $value'); }, childrenIfEnabled: [ CheckboxSettingsTile( leading: Icon(Icons.adb), settingKey: 'key-is-developer', title: 'Developer Mode', onChange: (value) { debugPrint('key-is-developer: $value'); }, ), SwitchSettingsTile( leading: Icon(Icons.usb), settingKey: 'key-is-usb-debugging', title: 'USB Debugging', onChange: (value) { debugPrint('key-is-usb-debugging: $value'); }, ), SimpleSettingsTile( title: 'Root Settings', subtitle: 'These settings is not accessible', enabled: false, ) ], ); ``` #### RadioSettingsTile RadioSettingsTile is a widget that has a list of Radio widgets with given title, subtitle and default/group value which determines which Radio will be selected initially. This widget supports Any type of values which should be put in the preference. However, since any type of value is supported, the input for this widget is a Map to the required values with their string representation. For example, if the required value type is a boolean then the values map can be as following: ```dart { true: 'Enabled', false: 'Disabled' } ``` So, if the `Enabled` value radio is selected then the value `true` will be stored in the preference Complete Example: ```dart RadioSettingsTile( title: 'Preferred Sync Period', settingKey: 'key-radio-sync-period', values: { 0: 'Never', 1: 'Daily', 7: 'Weekly', 15: 'Fortnight', 30: 'Monthly', }, selected: 0, onChange: (value) { debugPrint('key-radio-sync-period: $value days'); }, ) ``` #### DropDownSettingTile DropDownSettingsTile is a widget that has a list of DropdownMenuItems with given title, subtitle and default/group value which determines which value will be set to selected initially. This widget supports Any type of values which should be put in the preference. However, since any type of value is supported, the input for this widget is a Map to the required values with their string representation. For example, if the required value type is a boolean then the values map can be as following: ```dart { true: 'Enabled', false: 'Disabled' } ``` So, if the `Enabled` value is selected then the value `true` will be stored in the preference Complete Example: ```dart DropDownSettingsTile( title: 'E-Mail View', settingKey: 'key-dropdown-email-view', values: { 2: 'Simple', 3: 'Adjusted', 4: 'Normal', 5: 'Compact', 6: 'Squizzed', }, selected: 2, onChange: (value) { debugPrint('key-dropdown-email-view: $value'); }, ); ``` #### SliderSettingsTile SliderSettingsTile is a widget that has a slider given title, subtitle and default value which determines what the slider's position will be set initially. This widget supports double and integer types of values which should be put in the preference. Example: ```dart SliderSettingsTile( title: 'Volume', settingKey: 'key-slider-volume', defaultValue: 20, min: 0, max: 100, step: 1, leading: Icon(Icons.volume_up), onChange: (value) { debugPrint('key-slider-volume: $value'); }, ); ``` ## Modal widgets #### RadioModalSettingsTile RadioModalSettingsTile widget is the dialog version of the `RadioSettingsTile` widget. The use of this widget is similar to the RadioSettingsTile, only the displayed widget will be in a different position. i.e instead of inside the settings screen, it will be shown in a dialog above the settings screen. Example: ```dart RadioModalSettingsTile( title: 'Preferred Sync Period', settingKey: 'key-radio-sync-period', values: { 0: 'Never', 1: 'Daily', 7: 'Weekly', 15: 'Fortnight', 30: 'Monthly', }, selected: 0, onChange: (value) { debugPrint('key-radio-sync-period: $value days'); }, ); ``` #### SliderModalSettingsTile SliderModalSettingsTile widget is the dialog version of the SliderSettingsTile widget. The use of this widget is similar to the SliderSettingsTile, only the displayed widget will be in a different position. i.e instead of inside the settings screen, it will be shown in a dialog above the settings screen. Example: ```dart SliderSettingsTile( title: 'Volume', settingKey: 'key-slider-volume', defaultValue: 20, min: 0, max: 100, step: 1, leading: Icon(Icons.volume_up), onChange: (value) { debugPrint('key-slider-volume: $value'); }, ); ``` #### TextInputSettingsTile A Setting widget which allows user a text input in a TextFormField. Example: ```dart TextInputSettingsTile( title: 'User Name', settingKey: 'key-user-name', initialValue: 'admin', validator: (String username) { if (username != null && username.length > 3) { return null; } return "User Name can't be smaller than 4 letters"; }, borderColor: Colors.blueAccent, errorColor: Colors.deepOrangeAccent, ); ``` OR ``` dart TextInputSettingsTile( title: 'password', settingKey: 'key-user-password', obscureText: true, validator: (String password) { if (password != null && password.length > 6) { return null; } return "Password can't be smaller than 7 letters"; }, borderColor: Colors.blueAccent, errorColor: Colors.deepOrangeAccent, ); ``` #### ColorPickerSettingsTile ColorPickerSettingsTile is a widget which allows user to select a color from a set of Material color choices. Since, `Color` is an in-memory object type, the serialized version of the value of this widget will be a Hex value String of the selected color. For example, If selected color is `red` then the stored value will be "#ffff0000", but when retrieved, the value will be an instance of `Color` with properties of red color. This conversion string <-> color, makes this easy to check/debug the values from the storage/preference manually. The color panel shown in the widget is provided by the `flutter_material_color_picker` library. Example: ```dart ColorPickerSettingsTile( settingKey: 'key-color-picker', title: 'Accent Color', defaultValue: Colors.blue, onChange: (value) { debugPrint('key-color-picker: $value'); }, ); ``` ## Utility widgets #### SettingsScreen A simple Screen widget that may contain settings tiles or other widgets. The following example shows how you can create an empty settings screen with a title: ```dart SettingsScreen( title: "Application Settings", children: [], ); ``` Inside the children parameter, you can define settings tiles and other widgets. In this example we create a screen with a simple CheckboxSettingsTile in it: ```dart SettingsScreen( title: "Application Settings", children: CheckboxSettingsTile( settingKey: 'key-of-your-setting', title: 'This is a simple Checkbox', ), , ); ``` #### SettingsContainer A widget that helps its child or children to fin in the settings screen. It is helpful if you want to place other widgets than settings tiles in the settings screen body. The following example shows how you can create a container with one Text widget: ```dart SettingsContainer( child: Text('Hello world'), ); ``` In this example, we create a container with multiple Text widgets: ```dart SettingsContainer( children: Text('First line'), Text('Second line'), ], ); ``` ## Alternate widgets #### SimpleRadioSettingsTile SimpleRadioSettingsTile is a simpler version of the RadioSettingsTile. Instead of a Value-String map, this widget just takes a list of String values. In this widget, the displayed value and the stored value will be the same. Example: ```dart SimpleRadioSettingsTile( title: 'Sync Settings', settingKey: 'key-radio-sync-settings', values: [ 'Never', 'Daily', 'Weekly', 'Fortnight', 'Monthly', ], selected: 'Daily', onChange: (value) { debugPrint('key-radio-sync-settings: $value'); }, ); ``` #### SimpleDropDownSettingsTile SimpleDropDownSettingsTile is a simpler version of the DropDownSettingsTile. Instead of a Value-String map, this widget just takes a list of String values. In this widget, the displayed value and the stored value will be the same. Example: ```dart SimpleDropDownSettingsTile( title: 'Beauty Filter', settingKey: 'key-dropdown-beauty-filter', values: [ 'Simple', 'Normal', 'Little Special', 'Special', 'Extra Special', 'Bizarre', 'Horrific', ], selected: 'Special', onChange: (value) { debugPrint('key-dropdown-beauty-filter: $value'); }, ); ``` ## Contribution/Support - File an issue on the repository, if something is not working as expected. - Please follow the issue template used in flutter-sdk's repository, may be we'll integrate that here as well. - File an issue in the repository, If you have any suggestions and/or feature requests, use `[Suggestion]` or `[FeatureRequest]` tags in issue titles. - To support you just have to help out fellow developers on of the filed issues in this repository. - To contribute, just follow the standard open source contributions instructions, maybe we can follow the ones used in the flutter sdk. We'll see how it goes. **All help, issues, support and contributions are most welcome.** _If any one is interested in helping me maintain this library then please reach to me via comment on this [issue](https://github.com/GAM3RG33K/flutter_settings_screens/issues/86)._ ================================================ FILE: analysis_options.yaml ================================================ # This file configures the analyzer, which statically analyzes Dart code to # check for errors, warnings, and lints. # # The issues identified by the analyzer are surfaced in the UI of Dart-enabled # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be # invoked from the command line by running `flutter analyze`. # The following line activates a set of recommended lints for Flutter apps, # packages, and plugins designed to encourage good coding practices. include: package:flutter_lints/flutter.yaml linter: # The lint rules applied to this project can be customized in the # section below to disable rules from the `package:flutter_lints/flutter.yaml` # included above or to enable additional rules. A list of all available lints # and their documentation is published at https://dart.dev/lints. # # Instead of disabling a lint rule for the entire project in the # section below, it can also be suppressed for a single line of code # or a specific dart file by using the `// ignore: name_of_lint` and # `// ignore_for_file: name_of_lint` syntax on the line or in the file # producing the lint. rules: # avoid_print: false # Uncomment to disable the `avoid_print` rule # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options ================================================ FILE: example/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ .fvm/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related **/doc/api/ .dart_tool/ .flutter-plugins .packages .pub-cache/ .pub/ /build/ pubspec.lock # Web related lib/generated_plugin_registrant.dart # Exceptions to above rules. !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages ================================================ FILE: example/.metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: 2c7af1e24e45a79f4eb73d67d98fcecea8bf6146 channel: master project_type: app ================================================ FILE: example/README.md ================================================ # flutter_settings_screens_example Demonstrates how to use the flutter_settings_screens plugin. ## Getting Started This project is a starting point for a Flutter application. A few resources to get you started if this is your first Flutter project: - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) For help getting started with Flutter, view our [online documentation](https://flutter.dev/docs), which offers tutorials, samples, guidance on mobile development, and a full API reference. ================================================ FILE: example/android/.gitignore ================================================ gradle-wrapper.jar /.gradle /captures/ /gradlew /gradlew.bat /local.properties GeneratedPluginRegistrant.java # Remember to never publicly share your keystore. # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app key.properties **/*.keystore **/*.jks ================================================ FILE: example/android/app/build.gradle ================================================ plugins { id "com.android.application" id "dev.flutter.flutter-gradle-plugin" } def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) } } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { flutterVersionName = '1.0' } android { namespace "com.happyworks.flutter_settings_screens_example" compileSdkVersion flutter.compileSdkVersion compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } defaultConfig { applicationId "com.happyworks.flutter_settings_screens_example" minSdkVersion flutter.minSdkVersion targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName } buildTypes { release { // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug } } } flutter { source '../..' } dependencies { } ================================================ FILE: example/android/app/src/debug/AndroidManifest.xml ================================================ ================================================ FILE: example/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: example/android/app/src/main/java/com/happyworks/flutter_settings_screens_example/MainActivity.java ================================================ package com.happyworks.flutter_settings_screens_example; import io.flutter.embedding.android.FlutterActivity; public class MainActivity extends FlutterActivity { } ================================================ FILE: example/android/app/src/main/res/drawable/launch_background.xml ================================================ ================================================ FILE: example/android/app/src/main/res/drawable-v21/launch_background.xml ================================================ ================================================ FILE: example/android/app/src/main/res/values/styles.xml ================================================ ================================================ FILE: example/android/app/src/main/res/values-night/styles.xml ================================================ ================================================ FILE: example/android/app/src/profile/AndroidManifest.xml ================================================ ================================================ FILE: example/android/build.gradle ================================================ allprojects { repositories { google() mavenCentral() } } rootProject.layout.buildDirectory = '../build' subprojects { project.layout.buildDirectory = rootProject.layout.buildDirectory.dir(project.name) } subprojects { project.evaluationDependsOn(':app') } tasks.register("clean", Delete) { delete rootProject.layout.buildDirectory } ================================================ FILE: example/android/gradle/wrapper/gradle-wrapper.properties ================================================ #Fri Jun 23 08:50:38 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-all.zip ================================================ FILE: example/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true android.enableJetifier=true ================================================ FILE: example/android/settings.gradle ================================================ pluginManagement { def flutterSdkPath = { def properties = new Properties() file("local.properties").withInputStream { properties.load(it) } def flutterSdkPath = properties.getProperty("flutter.sdk") assert flutterSdkPath != null, "flutter.sdk not set in local.properties" return flutterSdkPath }() includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") repositories { google() mavenCentral() gradlePluginPortal() } } plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "com.android.application" version '8.4.0' apply false } include ':app' ================================================ FILE: example/ios/.gitignore ================================================ **/dgph *.mode1v3 *.mode2v3 *.moved-aside *.pbxuser *.perspectivev3 **/*sync/ .sconsign.dblite .tags* **/.vagrant/ **/DerivedData/ Icon? **/Pods/ **/.symlinks/ profile xcuserdata **/.generated/ Flutter/App.framework Flutter/Flutter.framework Flutter/Flutter.podspec Flutter/Generated.xcconfig Flutter/ephemeral/ Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ Flutter/flutter_export_environment.sh ServiceDefinitions.json Runner/GeneratedPluginRegistrant.* # Exceptions to above rules. !default.mode1v3 !default.mode2v3 !default.pbxuser !default.perspectivev3 ================================================ FILE: example/ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 MinimumOSVersion 12.0 ================================================ FILE: example/ios/Flutter/Debug.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" ================================================ FILE: example/ios/Flutter/Release.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" ================================================ FILE: example/ios/Podfile ================================================ # Uncomment this line to define a global platform for your project # platform :ios, '12.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_ios_podfile_setup target 'Runner' do use_frameworks! use_modular_headers! flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) end end ================================================ FILE: example/ios/Runner/AppDelegate.swift ================================================ import UIKit import Flutter @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } } ================================================ FILE: example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images": [ { "size": "20x20", "idiom": "iphone", "filename": "Icon-App-20x20@2x.png", "scale": "2x" }, { "size": "20x20", "idiom": "iphone", "filename": "Icon-App-20x20@3x.png", "scale": "3x" }, { "size": "29x29", "idiom": "iphone", "filename": "Icon-App-29x29@1x.png", "scale": "1x" }, { "size": "29x29", "idiom": "iphone", "filename": "Icon-App-29x29@2x.png", "scale": "2x" }, { "size": "29x29", "idiom": "iphone", "filename": "Icon-App-29x29@3x.png", "scale": "3x" }, { "size": "40x40", "idiom": "iphone", "filename": "Icon-App-40x40@2x.png", "scale": "2x" }, { "size": "40x40", "idiom": "iphone", "filename": "Icon-App-40x40@3x.png", "scale": "3x" }, { "size": "60x60", "idiom": "iphone", "filename": "Icon-App-60x60@2x.png", "scale": "2x" }, { "size": "60x60", "idiom": "iphone", "filename": "Icon-App-60x60@3x.png", "scale": "3x" }, { "size": "20x20", "idiom": "ipad", "filename": "Icon-App-20x20@1x.png", "scale": "1x" }, { "size": "20x20", "idiom": "ipad", "filename": "Icon-App-20x20@2x.png", "scale": "2x" }, { "size": "29x29", "idiom": "ipad", "filename": "Icon-App-29x29@1x.png", "scale": "1x" }, { "size": "29x29", "idiom": "ipad", "filename": "Icon-App-29x29@2x.png", "scale": "2x" }, { "size": "40x40", "idiom": "ipad", "filename": "Icon-App-40x40@1x.png", "scale": "1x" }, { "size": "40x40", "idiom": "ipad", "filename": "Icon-App-40x40@2x.png", "scale": "2x" }, { "size": "76x76", "idiom": "ipad", "filename": "Icon-App-76x76@1x.png", "scale": "1x" }, { "size": "76x76", "idiom": "ipad", "filename": "Icon-App-76x76@2x.png", "scale": "2x" }, { "size": "83.5x83.5", "idiom": "ipad", "filename": "Icon-App-83.5x83.5@2x.png", "scale": "2x" }, { "size": "1024x1024", "idiom": "ios-marketing", "filename": "Icon-App-1024x1024@1x.png", "scale": "1x" } ], "info": { "version": 1, "author": "xcode" } } ================================================ FILE: example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json ================================================ { "images": [ { "idiom": "universal", "filename": "LaunchImage.png", "scale": "1x" }, { "idiom": "universal", "filename": "LaunchImage@2x.png", "scale": "2x" }, { "idiom": "universal", "filename": "LaunchImage@3x.png", "scale": "3x" } ], "info": { "version": 1, "author": "xcode" } } ================================================ FILE: example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md ================================================ # Launch Screen Assets You can customize the launch screen with your own desired assets by replacing the image files in this directory. You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. ================================================ FILE: example/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: example/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: example/ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName Example CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName flutter_settings_screens_example CFBundlePackageType APPL CFBundleShortVersionString $(FLUTTER_BUILD_NAME) CFBundleSignature ???? CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance CADisableMinimumFrameDurationOnPhone UIApplicationSupportsIndirectInputEvents ================================================ FILE: example/ios/Runner/Runner-Bridging-Header.h ================================================ #import "GeneratedPluginRegistrant.h" ================================================ FILE: example/ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 2D304D1C1AD25E7DB8EB1CA0 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 56463CB180FDED103A6CB9C3 /* Pods_Runner.framework */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 17AD69071B14EF34D761EA0E /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 56463CB180FDED103A6CB9C3 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; B7FD848EF692798DFEB371FA /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; D4AD61949C3A50BB5618FC8E /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 2D304D1C1AD25E7DB8EB1CA0 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, ); name = Flutter; sourceTree = ""; }; 974384AC923C661FE6D16947 /* Frameworks */ = { isa = PBXGroup; children = ( 56463CB180FDED103A6CB9C3 /* Pods_Runner.framework */, ); name = Frameworks; sourceTree = ""; }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, C03ACAA41C034593E01687FB /* Pods */, 974384AC923C661FE6D16947 /* Frameworks */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, ); name = Products; sourceTree = ""; }; 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, ); path = Runner; sourceTree = ""; }; C03ACAA41C034593E01687FB /* Pods */ = { isa = PBXGroup; children = ( D4AD61949C3A50BB5618FC8E /* Pods-Runner.debug.xcconfig */, 17AD69071B14EF34D761EA0E /* Pods-Runner.release.xcconfig */, B7FD848EF692798DFEB371FA /* Pods-Runner.profile.xcconfig */, ); name = Pods; path = Pods; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( 17F39D70C0DB2A58A43E2364 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, E0B88306847A324AD9DFA0C9 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 17F39D70C0DB2A58A43E2364 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Run Script"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; E0B88306847A324AD9DFA0C9 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97C146FB1CF9000F007C117D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 97C147001CF9000F007C117D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Profile; }; 249021D4217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.example; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Profile; }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.example; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.example; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, 249021D3217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, 249021D4217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } ================================================ FILE: example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ PreviewsEnabled ================================================ FILE: example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: example/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ PreviewsEnabled ================================================ FILE: example/lib/app_settings_page.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_settings_screens/flutter_settings_screens.dart'; class AppSettings extends StatefulWidget { @override _AppSettingsState createState() => _AppSettingsState(); } class _AppSettingsState extends State { @override Widget build(BuildContext context) { return Container( child: SettingsScreen( title: 'Application Settings', children: [ SettingsGroup( title: 'Single Choice Settings', children: [ SwitchSettingsTile( settingKey: 'key-wifi', title: 'Wi-Fi', subtitle: 'Wi-Fi allows interacting with the local network ' 'or internet via connecting to a W-Fi router', enabledLabel: 'Enabled', disabledLabel: 'Disabled', leading: Icon(Icons.wifi), onChange: (value) { debugPrint('key-wifi: $value'); }, ), CheckboxSettingsTile( settingKey: 'key-blue-tooth', title: 'Bluetooth', subtitle: 'Bluetooth allows interacting with the ' 'near by bluetooth enabled devices', enabledLabel: 'Enabled', disabledLabel: 'Disabled', leading: Icon(Icons.bluetooth), onChange: (value) { debugPrint('key-blue-tooth: $value'); }, ), SwitchSettingsTile( leading: Icon(Icons.developer_mode), settingKey: 'key-switch-dev-mode', title: 'Developer Settings', onChange: (value) { debugPrint('key-switch-dev-mod: $value'); }, childrenIfEnabled: [ CheckboxSettingsTile( leading: Icon(Icons.adb), settingKey: 'key-is-developer', title: 'Developer Mode', defaultValue: true, onChange: (value) { debugPrint('key-is-developer: $value'); }, ), SwitchSettingsTile( leading: Icon(Icons.usb), settingKey: 'key-is-usb-debugging', title: 'USB Debugging', onChange: (value) { debugPrint('key-is-usb-debugging: $value'); }, ), SimpleSettingsTile( title: 'Root Settings', subtitle: 'These setting is not accessible', enabled: false, ), SimpleSettingsTile( title: 'Custom Settings', subtitle: 'Tap to execute custom callback', onTap: () => debugPrint('Custom action'), ), ], ), SimpleSettingsTile( title: 'More Settings', subtitle: 'General App Settings', child: SettingsScreen( title: 'App Settings', children: [ CheckboxSettingsTile( leading: Icon(Icons.adb), settingKey: 'key-is-developer', title: 'Developer Mode', onChange: (bool value) { debugPrint('Developer Mode ${value ? 'on' : 'off'}'); }, ), SwitchSettingsTile( leading: Icon(Icons.usb), settingKey: 'key-is-usb-debugging', title: 'USB Debugging', onChange: (value) { debugPrint('USB Debugging: $value'); }, ), ], ), ), TextInputSettingsTile( title: 'User Name', settingKey: 'key-user-name', initialValue: 'admin', validator: (String? username) { if (username != null && username.length > 3) { return null; } return "User Name can't be smaller than 4 letters"; }, borderColor: Colors.blueAccent, errorColor: Colors.deepOrangeAccent, ), TextInputSettingsTile( title: 'password', settingKey: 'key-user-password', obscureText: true, validator: (String? password) { if (password != null && password.length > 6) { return null; } return "Password can't be smaller than 7 letters"; }, borderColor: Colors.blueAccent, errorColor: Colors.deepOrangeAccent, ), ModalSettingsTile( title: 'Quick setting dialog', subtitle: 'Settings on a dialog', children: [ CheckboxSettingsTile( settingKey: 'key-day-light-savings', title: 'Daylight Time Saving', enabledLabel: 'Enabled', disabledLabel: 'Disabled', leading: Icon(Icons.timelapse), onChange: (value) { debugPrint('key-day-light-saving: $value'); }, ), SwitchSettingsTile( settingKey: 'key-dark-mode', title: 'Dark Mode', enabledLabel: 'Enabled', disabledLabel: 'Disabled', leading: Icon(Icons.palette), onChange: (value) { debugPrint('jey-dark-mode: $value'); }, ), ], ), ExpandableSettingsTile( title: 'Quick setting 2', subtitle: 'Expandable Settings', expanded: true, children: [ CheckboxSettingsTile( settingKey: 'key-day-light-savings-2', title: 'Daylight Time Saving', enabledLabel: 'Enabled', disabledLabel: 'Disabled', leading: Icon(Icons.timelapse), onChange: (value) { debugPrint('key-day-light-savings-2: $value'); }, ), SwitchSettingsTile( settingKey: 'key-dark-mode-2', title: 'Dark Mode', enabledLabel: 'Enabled', disabledLabel: 'Disabled', leading: Icon(Icons.palette), onChange: (value) { debugPrint('key-dark-mode-2: $value'); }, ), ], ), ], ), SettingsGroup( title: 'Multiple choice settings', children: [ RadioSettingsTile( title: 'Preferred Sync Period', settingKey: 'key-radio-sync-period', values: { 0: 'Never', 1: 'Daily', 7: 'Weekly', 15: 'Fortnight', 30: 'Monthly', }, selected: 0, onChange: (value) { debugPrint('key-radio-sync-period: $value'); }, ), DropDownSettingsTile( title: 'E-Mail View', settingKey: 'key-dropdown-email-view', values: { 2: 'Simple', 3: 'Adjusted', 4: 'Normal', 5: 'Compact', 6: 'Squizzed', }, selected: 2, onChange: (value) { debugPrint('key-dropdown-email-view: $value'); }, ), ], ), ModalSettingsTile( title: 'Group Settings', subtitle: 'Same group settings but in a dialog', children: [ SimpleRadioSettingsTile( title: 'Sync Settings', settingKey: 'key-radio-sync-settings', values: [ 'Never', 'Daily', 'Weekly', 'Fortnight', 'Monthly', ], selected: 'Daily', onChange: (value) { debugPrint('key-radio-sync-settings: $value'); }, ), SimpleDropDownSettingsTile( title: 'Beauty Filter', settingKey: 'key-dropdown-beauty-filter', values: [ 'Simple', 'Normal', 'Little Special', 'Special', 'Extra Special', 'Bizarre', 'Horrific', ], selected: 'Special', onChange: (value) { debugPrint('key-dropdown-beauty-filter: $value'); }, ) ], ), ExpandableSettingsTile( title: 'Expandable Group Settings', subtitle: 'Group of settings (expandable)', children: [ RadioSettingsTile( title: 'Beauty Filter', settingKey: 'key-radio-beauty-filter-expandable', values: { 1.0: 'Simple', 1.5: 'Normal', 2.0: 'Little Special', 2.5: 'Special', 3.0: 'Extra Special', 3.5: 'Bizarre', 4.0: 'Horrific', }, selected: 2.5, onChange: (value) { debugPrint('key-radio-beauty-filter-expandable: $value'); }, ), DropDownSettingsTile( title: 'Preferred Sync Period', settingKey: 'key-dropdown-sync-period-2', values: { 0: 'Never', 1: 'Daily', 7: 'Weekly', 15: 'Fortnight', 30: 'Monthly', }, selected: 0, onChange: (value) { debugPrint('key-dropdown-sync-period-2: $value'); }, ) ], ), SettingsGroup( title: 'Other settings', children: [ SliderSettingsTile( title: 'Volume [Auto-Adjusting to 20]', settingKey: 'key-slider-volume', defaultValue: 20, min: 0, max: 100, step: 1, leading: Icon(Icons.volume_up), decimalPrecision: 0, onChange: (value) { debugPrint('\n===== on change end =====\n' 'key-slider-volume: $value' '\n==========\n'); Future.delayed(Duration(seconds: 1), () { // Reset value only if the current value is not 20 if (Settings.getValue('key-slider-volume') != 20) { debugPrint('\n===== on change end =====\n' 'Resetting value to 20' '\n==========\n'); Settings.setValue('key-slider-volume', 20.0, notify: true); } }); }, ), ColorPickerSettingsTile( settingKey: 'key-color-picker', title: 'Accent Color', defaultValue: Colors.blue, onChange: (value) { debugPrint('key-color-picker: $value'); }, ) ], ), ModalSettingsTile( title: 'Other settings', subtitle: 'Other Settings in a Dialog', children: [ SliderSettingsTile( title: 'Custom Ratio', settingKey: 'key-custom-ratio-slider-2', defaultValue: 2.5, min: 1, max: 5, step: 0.1, decimalPrecision: 1, leading: Icon(Icons.aspect_ratio), onChange: (value) { debugPrint('\n===== on change =====\n' 'key-custom-ratio-slider-2: $value' '\n==========\n'); }, onChangeStart: (value) { debugPrint('\n===== on change start =====\n' 'key-custom-ratio-slider-2: $value' '\n==========\n'); }, onChangeEnd: (value) { debugPrint('\n===== on change end =====\n' 'key-custom-ratio-slider-2: $value' '\n==========\n'); }, ), ColorPickerSettingsTile( settingKey: 'key-color-picker-2', title: 'Accent Picker', defaultValue: Colors.blue, onChange: (value) { debugPrint('key-color-picker-2: $value'); }, ) ], ) ], ), ); } } ================================================ FILE: example/lib/cache_provider.dart ================================================ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_settings_screens/flutter_settings_screens.dart'; import 'package:hive/hive.dart'; import 'package:path_provider/path_provider.dart'; /// A cache access provider class for shared preferences using Hive library class HiveCache extends CacheProvider { Box? _preferences; final String keyName = 'app_preferences'; @override Future init() async { WidgetsFlutterBinding.ensureInitialized(); if (!kIsWeb) { final defaultDirectory = await getApplicationDocumentsDirectory(); Hive.init(defaultDirectory.path); } if (Hive.isBoxOpen(keyName)) { _preferences = Hive.box(keyName); } else { _preferences = await Hive.openBox(keyName); } } Set get keys => getKeys(); @override bool? getBool(String key, {bool? defaultValue}) { return _preferences?.get(key); } @override double? getDouble(String key, {double? defaultValue}) { return _preferences?.get(key); } @override int? getInt(String key, {int? defaultValue}) { return _preferences?.get(key); } @override String? getString(String key, {String? defaultValue}) { return _preferences?.get(key); } @override Future setBool(String key, bool? value) async { await _preferences?.put(key, value); } @override Future setDouble(String key, double? value) async { await _preferences?.put(key, value); } @override Future setInt(String key, int? value) async { await _preferences?.put(key, value); } @override Future setString(String key, String? value) async { await _preferences?.put(key, value); } @override Future setObject(String key, T? value) async { await _preferences?.put(key, value); } @override bool containsKey(String key) { return _preferences?.containsKey(key) ?? false; } @override Set getKeys() { return _preferences?.keys.toSet() ?? {}; } @override Future remove(String key) async { if (containsKey(key)) { await _preferences?.delete(key); } } @override Future removeAll() async { final keys = getKeys(); await _preferences?.deleteAll(keys); } @override T? getValue(String key, {T? defaultValue}) { var value = _preferences?.get(key); if (value is T) { return value; } return defaultValue; } } ================================================ FILE: example/lib/main.dart ================================================ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_settings_screens/flutter_settings_screens.dart'; import 'app_settings_page.dart'; import 'cache_provider.dart'; void main() { initSettings().then((accentColor) { runApp(MyApp(accentColor: accentColor)); }); } Future> initSettings() async { await Settings.init( cacheProvider: _isUsingHive ? HiveCache() : SharePreferenceCache(), ); final _accentColor = ValueNotifier(Colors.blueAccent); return _accentColor; } bool _isDarkTheme = true; bool _isUsingHive = true; class MyApp extends StatelessWidget { final ValueNotifier accentColor; const MyApp({Key? key, required this.accentColor}) : super(key: key); // This widget is the root of your application. @override Widget build(BuildContext context) { return MyHomePage( title: 'Flutter Demo Home Page', accentColor: accentColor, ); } } class MyHomePage extends StatefulWidget { final String title; final ValueNotifier accentColor; const MyHomePage({ Key? key, required this.accentColor, required this.title, }) : super(key: key); @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State { @override Widget build(BuildContext context) { return ValueListenableBuilder( valueListenable: widget.accentColor, builder: (_, color, __) { final _darkTheme = ThemeData.dark(); final _lightTheme = ThemeData.light(); return MaterialApp( title: 'App Settings Demo', theme: _isDarkTheme ? _darkTheme.copyWith( colorScheme: _darkTheme.colorScheme.copyWith( secondary: color, ), checkboxTheme: CheckboxThemeData( fillColor: MaterialStateProperty.resolveWith( (Set states) { if (states.contains(MaterialState.disabled)) { return null; } if (states.contains(MaterialState.selected)) { return color; } return null; }), ), radioTheme: RadioThemeData( fillColor: MaterialStateProperty.resolveWith( (Set states) { if (states.contains(MaterialState.disabled)) { return null; } if (states.contains(MaterialState.selected)) { return color; } return null; }), ), switchTheme: SwitchThemeData( thumbColor: MaterialStateProperty.resolveWith( (Set states) { if (states.contains(MaterialState.disabled)) { return null; } if (states.contains(MaterialState.selected)) { return color; } return null; }), trackColor: MaterialStateProperty.resolveWith( (Set states) { if (states.contains(MaterialState.disabled)) { return null; } if (states.contains(MaterialState.selected)) { return color; } return null; }), ), ) : _lightTheme.copyWith( colorScheme: _darkTheme.colorScheme.copyWith( secondary: color, ), checkboxTheme: CheckboxThemeData( fillColor: MaterialStateProperty.resolveWith( (Set states) { if (states.contains(MaterialState.disabled)) { return null; } if (states.contains(MaterialState.selected)) { return color; } return null; }), ), radioTheme: RadioThemeData( fillColor: MaterialStateProperty.resolveWith( (Set states) { if (states.contains(MaterialState.disabled)) { return null; } if (states.contains(MaterialState.selected)) { return color; } return null; }), ), switchTheme: SwitchThemeData( thumbColor: MaterialStateProperty.resolveWith( (Set states) { if (states.contains(MaterialState.disabled)) { return null; } if (states.contains(MaterialState.selected)) { return color; } return null; }), trackColor: MaterialStateProperty.resolveWith( (Set states) { if (states.contains(MaterialState.disabled)) { return null; } if (states.contains(MaterialState.selected)) { return color; } return null; }), ), ), home: Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( children: [ _buildThemeSwitch(context), _buildPreferenceSwitch(context), SizedBox( height: 50.0, ), AppBody(), ], ), ), ), ); }, ); } Widget _buildPreferenceSwitch(BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Text('Shared Pref'), Switch( activeColor: Theme.of(context).colorScheme.secondary, value: _isUsingHive, onChanged: (newVal) { if (kIsWeb) { return; } _isUsingHive = newVal; setState(() { initSettings(); }); }), Text('Hive Storage'), ], ); } Widget _buildThemeSwitch(BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Text('Light Theme'), Switch( activeColor: Theme.of(context).colorScheme.secondary, value: _isDarkTheme, onChanged: (newVal) { _isDarkTheme = newVal; setState(() {}); }), Text('Dark Theme'), ], ); } } class AppBody extends StatefulWidget { @override _AppBodyState createState() => _AppBodyState(); } class _AppBodyState extends State { @override Widget build(BuildContext context) { return Column( children: [ _buildClearCacheButton(context), SizedBox( height: 25.0, ), ElevatedButton( onPressed: () { openAppSettings(context); }, child: Text('Start Demo'), ), ], ); } void openAppSettings(BuildContext context) { Navigator.of(context).push(MaterialPageRoute( builder: (context) => AppSettings(), )); } Widget _buildClearCacheButton(BuildContext context) { return ElevatedButton( onPressed: () { Settings.clearCache(); showSnackBar( context, 'Cache cleared for selected cache.', ); }, child: Text('Clear selected Cache'), ); } } void showSnackBar(BuildContext context, String message) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( message, style: TextStyle( color: Colors.white, ), ), backgroundColor: Theme.of(context).primaryColor, ), ); } ================================================ FILE: example/macos/.gitignore ================================================ # Flutter-related **/Flutter/ephemeral/ **/Pods/ # Xcode-related **/dgph **/xcuserdata/ ================================================ FILE: example/macos/Flutter/Flutter-Debug.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: example/macos/Flutter/Flutter-Release.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: example/macos/Flutter/GeneratedPluginRegistrant.swift ================================================ // // Generated file. Do not edit. // import FlutterMacOS import Foundation import path_provider_foundation import shared_preferences_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) } ================================================ FILE: example/macos/Podfile ================================================ platform :osx, '10.11' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_macos_podfile_setup target 'Runner' do use_frameworks! use_modular_headers! flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_macos_build_settings(target) end end ================================================ FILE: example/macos/Runner/AppDelegate.swift ================================================ import Cocoa import FlutterMacOS @NSApplicationMain class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } } ================================================ FILE: example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images": [ { "size": "16x16", "idiom": "mac", "filename": "app_icon_16.png", "scale": "1x" }, { "size": "16x16", "idiom": "mac", "filename": "app_icon_32.png", "scale": "2x" }, { "size": "32x32", "idiom": "mac", "filename": "app_icon_32.png", "scale": "1x" }, { "size": "32x32", "idiom": "mac", "filename": "app_icon_64.png", "scale": "2x" }, { "size": "128x128", "idiom": "mac", "filename": "app_icon_128.png", "scale": "1x" }, { "size": "128x128", "idiom": "mac", "filename": "app_icon_256.png", "scale": "2x" }, { "size": "256x256", "idiom": "mac", "filename": "app_icon_256.png", "scale": "1x" }, { "size": "256x256", "idiom": "mac", "filename": "app_icon_512.png", "scale": "2x" }, { "size": "512x512", "idiom": "mac", "filename": "app_icon_512.png", "scale": "1x" }, { "size": "512x512", "idiom": "mac", "filename": "app_icon_1024.png", "scale": "2x" } ], "info": { "version": 1, "author": "xcode" } } ================================================ FILE: example/macos/Runner/Base.lproj/MainMenu.xib ================================================ ================================================ FILE: example/macos/Runner/Configs/AppInfo.xcconfig ================================================ // Application-level settings for the Runner target. // // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the // future. If not, the values below would default to using the project name when this becomes a // 'flutter create' template. // The application's name. By default this is also the title of the Flutter window. PRODUCT_NAME = example // The application's bundle identifier PRODUCT_BUNDLE_IDENTIFIER = com.example.example // The copyright displayed in application information PRODUCT_COPYRIGHT = Copyright © 2022 com.example. All rights reserved. ================================================ FILE: example/macos/Runner/Configs/Debug.xcconfig ================================================ #include "../../Flutter/Flutter-Debug.xcconfig" #include "Warnings.xcconfig" ================================================ FILE: example/macos/Runner/Configs/Release.xcconfig ================================================ #include "../../Flutter/Flutter-Release.xcconfig" #include "Warnings.xcconfig" ================================================ FILE: example/macos/Runner/Configs/Warnings.xcconfig ================================================ WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings GCC_WARN_UNDECLARED_SELECTOR = YES CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE CLANG_WARN__DUPLICATE_METHOD_MATCH = YES CLANG_WARN_PRAGMA_PACK = YES CLANG_WARN_STRICT_PROTOTYPES = YES CLANG_WARN_COMMA = YES GCC_WARN_STRICT_SELECTOR_MATCH = YES CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES GCC_WARN_SHADOW = YES CLANG_WARN_UNREACHABLE_CODE = YES ================================================ FILE: example/macos/Runner/DebugProfile.entitlements ================================================ com.apple.security.app-sandbox com.apple.security.cs.allow-jit com.apple.security.network.server ================================================ FILE: example/macos/Runner/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIconFile CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString $(FLUTTER_BUILD_NAME) CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright $(PRODUCT_COPYRIGHT) NSMainNibFile MainMenu NSPrincipalClass NSApplication ================================================ FILE: example/macos/Runner/MainFlutterWindow.swift ================================================ import Cocoa import FlutterMacOS class MainFlutterWindow: NSWindow { override func awakeFromNib() { let flutterViewController = FlutterViewController.init() let windowFrame = self.frame self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) RegisterGeneratedPlugins(registry: flutterViewController) super.awakeFromNib() } } ================================================ FILE: example/macos/Runner/Release.entitlements ================================================ com.apple.security.app-sandbox ================================================ FILE: example/macos/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 51; objects = { /* Begin PBXAggregateTarget section */ 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { isa = PBXAggregateTarget; buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; buildPhases = ( 33CC111E2044C6BF0003C045 /* ShellScript */, ); dependencies = ( ); name = "Flutter Assemble"; productName = FLX; }; /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; proxyType = 1; remoteGlobalIDString = 33CC111A2044C6BA0003C045; remoteInfo = FLX; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 33CC110E2044A8840003C045 /* Bundle Framework */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Bundle Framework"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 33CC10EA2044A3C60003C045 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 33BA886A226E78AF003329D5 /* Configs */ = { isa = PBXGroup; children = ( 33E5194F232828860026EE4D /* AppInfo.xcconfig */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, ); path = Configs; sourceTree = ""; }; 33CC10E42044A3C60003C045 = { isa = PBXGroup; children = ( 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, ); sourceTree = ""; }; 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( 33CC10ED2044A3C60003C045 /* example.app */, ); name = Products; sourceTree = ""; }; 33CC11242044D66E0003C045 /* Resources */ = { isa = PBXGroup; children = ( 33CC10F22044A3C60003C045 /* Assets.xcassets */, 33CC10F42044A3C60003C045 /* MainMenu.xib */, 33CC10F72044A3C60003C045 /* Info.plist */, ); name = Resources; path = ..; sourceTree = ""; }; 33CEB47122A05771004F2AC0 /* Flutter */ = { isa = PBXGroup; children = ( 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, ); path = Flutter; sourceTree = ""; }; 33FAB671232836740065AC1E /* Runner */ = { isa = PBXGroup; children = ( 33CC10F02044A3C60003C045 /* AppDelegate.swift */, 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, 33E51913231747F40026EE4D /* DebugProfile.entitlements */, 33E51914231749380026EE4D /* Release.entitlements */, 33CC11242044D66E0003C045 /* Resources */, 33BA886A226E78AF003329D5 /* Configs */, ); path = Runner; sourceTree = ""; }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 33CC10EC2044A3C60003C045 /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, ); buildRules = ( ); dependencies = ( 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; productName = Runner; productReference = 33CC10ED2044A3C60003C045 /* example.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 33CC10E52044A3C60003C045 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; LastUpgradeCheck = 1300; ORGANIZATIONNAME = ""; TargetAttributes = { 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1100; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.Sandbox = { enabled = 1; }; }; }; 33CC111A2044C6BA0003C045 = { CreatedOnToolsVersion = 9.2; ProvisioningStyle = Manual; }; }; }; buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 33CC10E42044A3C60003C045; productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 33CC10EC2044A3C60003C045 /* Runner */, 33CC111A2044C6BA0003C045 /* Flutter Assemble */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 33CC10EB2044A3C60003C045 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; }; 33CC111E2044C6BF0003C045 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( Flutter/ephemeral/FlutterInputs.xcfilelist, ); inputPaths = ( Flutter/ephemeral/tripwire, ); outputFileListPaths = ( Flutter/ephemeral/FlutterOutputs.xcfilelist, ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 33CC10E92044A3C60003C045 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { isa = PBXVariantGroup; children = ( 33CC10F52044A3C60003C045 /* Base */, ); name = MainMenu.xib; path = Runner; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 338D0CE9231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Profile; }; 338D0CEA231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; name = Profile; }; 338D0CEB231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Manual; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Profile; }; 33CC10F92044A3C60003C045 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 33CC10FA2044A3C60003C045 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Release; }; 33CC10FC2044A3C60003C045 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; name = Debug; }; 33CC10FD2044A3C60003C045 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; name = Release; }; 33CC111C2044C6BA0003C045 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Manual; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 33CC111D2044C6BA0003C045 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC10F92044A3C60003C045 /* Debug */, 33CC10FA2044A3C60003C045 /* Release */, 338D0CE9231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC10FC2044A3C60003C045 /* Debug */, 33CC10FD2044A3C60003C045 /* Release */, 338D0CEA231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC111C2044C6BA0003C045 /* Debug */, 33CC111D2044C6BA0003C045 /* Release */, 338D0CEB231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } ================================================ FILE: example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: example/macos/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: example/pubspec.yaml ================================================ name: flutter_settings_screens_example description: Demonstrates flutter_settings_screens plugin features. publish_to: 'none' environment: sdk: '>=3.2.4 <4.0.0' dependencies: flutter: sdk: flutter # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.0 #library to store the preference/settings using hive library hive_flutter: ^1.0.0 dev_dependencies: flutter_test: sdk: flutter flutter_settings_screens: path: ../ # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter. flutter: # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in # the material Icons class. uses-material-design: true # To add assets to your application, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware. # For details regarding adding assets from package dependencies, see # https://flutter.dev/assets-and-images/#from-packages # To add custom fonts to your application, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: # fonts: # - family: Schyler # fonts: # - asset: fonts/Schyler-Regular.ttf # - asset: fonts/Schyler-Italic.ttf # style: italic # - family: Trajan Pro # fonts: # - asset: fonts/TrajanPro.ttf # - asset: fonts/TrajanPro_Bold.ttf # weight: 700 # # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages ================================================ FILE: example/test/widget_test.dart ================================================ //// This is a basic Flutter widget test. //// //// To perform an interaction with a widget in your test, use the WidgetTester //// utility that Flutter provides. For example, you can send tap and scroll //// gestures. You can also use WidgetTester to find child widgets in the widget //// tree, read text, and verify that the values of widget properties are correct. // //import 'package:flutter/material.dart'; //import 'package:flutter_test/flutter_test.dart'; // //import 'package:flutter_settings_screens_example/main.dart'; // //void main() { // testWidgets('Verify Platform version', (WidgetTester tester) async { // // Build our app and trigger a frame. // await tester.pumpWidget(MyApp()); // // // Verify that platform version is retrieved. // expect( // find.byWidgetPredicate( // (Widget widget) => widget is Text && // widget.data.startsWith('Running on:'), // ), // findsOneWidget, // ); // }); //} ================================================ FILE: example/web/index.html ================================================ example ================================================ FILE: example/web/manifest.json ================================================ { "name": "example", "short_name": "example", "start_url": ".", "display": "standalone", "background_color": "#0175C2", "theme_color": "#0175C2", "description": "A new Flutter project.", "orientation": "portrait-primary", "prefer_related_applications": false, "icons": [ { "src": "icons/Icon-192.png", "sizes": "192x192", "type": "image/png" }, { "src": "icons/Icon-512.png", "sizes": "512x512", "type": "image/png" }, { "src": "icons/Icon-maskable-192.png", "sizes": "192x192", "type": "image/png", "purpose": "maskable" }, { "src": "icons/Icon-maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" } ] } ================================================ FILE: lib/flutter_settings_screens.dart ================================================ /// Settings Screen with Custom Storage Interface /// /// Author: Harshvardhan Joshi /// /// Based on the plugin by Barnabás BARTHA : /// https://github.com/BarthaBRW/shared_preferences_settings library flutter_settings_screens; export 'src/cache/cache.dart'; export 'src/settings.dart'; export 'src/utils/utils.dart'; export 'src/widgets/settings_widgets.dart'; ================================================ FILE: lib/src/cache/cache.dart ================================================ export 'cache_provider.dart'; export 'cache_provider_impl.dart'; ================================================ FILE: lib/src/cache/cache_provider.dart ================================================ import 'package:flutter_settings_screens/flutter_settings_screens.dart'; /// This is an abstract class to provide access of storage/preferences platform /// from the developer's existing app to this settings screen /// /// For example: /// the developer can choose to provide the existing preference platform /// access by providing the implementation for this class. /// /// if the developer is using the `shared_preferences` library, then the implementation /// of one of the methods would look like this: /// ``` dart /// // SharedPreferences _preferences = await SharedPreferences.getInstance(); /// /// String? getString(String key, {String? defaultValue}) { /// return _preferences.getString(key) ?? defaultValue; /// } /// /// Future setBool(String key, bool? value) async { /// await _preferences.setBool(key, value); /// } /// ``` /// /// and if the developer is using the Hive library or storing preferences/data /// then the implementation would look like this: /// /// ```dart /// //Box _preferences = await Hive.openBox(keyName); /// String? getString(String key, {String? defaultValue}) { /// return _preferences.get(key) ?? defaultValue; /// } /// /// Future setBool(String key, bool? value) async { /// await _preferences.put(key, value); /// } /// ``` /// /// Similarly, if the developer is using any other type of library for this purpose, /// just providing the implementation using that library will be sufficient for /// using the settings screen /// /// /// For more details on how to properly implement this class, Check out the /// `cache_provider.dart` file in the example code of this library along with /// the existing [SharePreferenceCache] implementation. /// abstract class CacheProvider { CacheProvider(); /// Method to perform initializations regarding the underlying storage systems /// ex. Creating instance of the SharedPreferences or Hive boxes /// /// This method must be called one the cache provider implementation to ensure /// smooth operation. Future init(); /// Method to get Int value from the storage, if the retrieved value is null /// then [defaultValue] should be used, if provided. /// /// /// Example code(SharedPreferences Implementation): /// /// ```dart /// int? getInt(String key, {int? defaultValue}) { /// return _preferences.getInt(key) ?? defaultValue; /// } /// ``` /// /// The [defaultValue] is an optional & nullable value, you may choose not to provide it /// in which case the null value will be returned as result. /// /// Output can be represented like this: /// getterOutput -if-absent-> defaultValue -if-absent-> null /// /// here [getterOutput] is the expected output of the getter method called. /// The method must be implementation of one of the definitions from the cache provider /// int? getInt(String key, {int? defaultValue}); /// Method to get String value from the storage, if the retrieved value is null /// then [defaultValue] should be used, if provided. /// /// /// Example code(SharedPreferences Implementation): /// /// ```dart /// String? getString(String key, {String? defaultValue}) { /// return _preferences.getString(key) ?? defaultValue; /// } /// ``` /// /// The [defaultValue] is an optional & nullable value, you may choose not to provide it /// in which case the null value will be returned as result. /// /// Output can be represented like this: /// getterOutput -if-absent-> defaultValue -if-absent-> null /// /// here [getterOutput] is the expected output of the getter method called. /// The method must be implementation of one of the definitions from the cache provider /// String? getString(String key, {String? defaultValue}); /// Method to get double value from the storage, if the retrieved value is null /// then [defaultValue] should be used, if provided. /// /// /// Example code(SharedPreferences Implementation): /// /// ```dart /// double? getDouble(String key, {double? defaultValue}) { /// return _preferences.getDouble(key) ?? defaultValue; /// } /// ``` /// /// The [defaultValue] is an optional & nullable value, you may choose not to provide it /// in which case the null value will be returned as result. /// /// Output can be represented like this: /// getterOutput -if-absent-> defaultValue -if-absent-> null /// /// here [getterOutput] is the expected output of the getter method called. /// The method must be implementation of one of the definitions from the cache provider /// double? getDouble(String key, {double? defaultValue}); /// Method to get boolean value from the storage, if the retrieved value is null /// then [defaultValue] should be used, if provided. /// /// /// Example code(SharedPreferences Implementation): /// /// ```dart /// double? getBool(String key, {bool? defaultValue}) { /// return _preferences.getBool(key) ?? defaultValue; /// } /// ``` /// /// The [defaultValue] is an optional & nullable value, you may choose not to provide it /// in which case the null value will be returned as result. /// /// Output can be represented like this: /// getterOutput -if-absent-> defaultValue -if-absent-> null /// /// here [getterOutput] is the expected output of the getter method called. /// The method must be implementation of one of the definitions from the cache provider /// bool? getBool(String key, {bool? defaultValue}); /// Method to set int value to the storage, value can be null according /// to the support by underlying storage system. /// /// /// Example code(SharedPreferences Implementation): /// /// ```dart /// Future getInt(String key, int? value) async { /// await _preferences.setInt(key, value); /// } /// ``` /// Future setInt(String key, int? value); /// Method to set String value to the storage, value can be null according /// to the support by underlying storage system. /// /// /// Example code(SharedPreferences Implementation): /// /// ```dart /// Future setString(String key, String? value) async { /// await _preferences.setString(key, value); /// } /// ``` /// Future setString(String key, String? value); /// Method to set Double value to the storage, value can be null according /// to the support by underlying storage system. /// /// /// Example code(SharedPreferences Implementation): /// /// ```dart /// Future setDouble(String key, double? value) async { /// await _preferences.setDouble(key, value); /// } /// ``` /// Future setDouble(String key, double? value); /// Method to set boolean value to the storage, value can be null according /// to the support by underlying storage system. /// /// /// Example code(SharedPreferences Implementation): /// /// ```dart /// Future setBool(String key, bool? value) async { /// await _preferences.setBool(key, value); /// } /// ``` /// Future setBool(String key, bool? value); /// This method is used to check if a specific [key] is available/stored /// in the underlying storage bool containsKey(String key); /// This method is used to get a set of available/stored keys /// in the underlying storage. /// /// This method makes easy to iterate over stored keys for various /// purposes. /// /// For example: finding a set of keys from all the available /// keys that matches a regular expression Set getKeys(); /// This method is used to remove provided [key] from the underlying storage system. /// /// For some storage systems, this call can be equivalent to setting null as /// value for the given [key] /// Future remove(String key); /// This method is used to remove all the available/stored keys from the /// underlying storage system. /// /// Implementing this method can utilize the [getKeys] method to iterate & remove /// or can use interface methods with same purpose provided by the underlying /// storage system Future removeAll(); /// Method to set custom object value to the storage, value can be null according /// to the support by underlying storage system. /// /// Example code(SharedPreferences Implementation): /// /// ```dart /// Future setObject(String key, T? value) async { /// if (T is int || value is int) { /// await _preferences?.setInt(key, value as int); /// } /// if (T is double || value is double) { /// await _preferences?.setDouble(key, value as double); /// } /// if (T is bool || value is bool) { /// await _preferences?.setBool(key, value as bool); /// } /// if (T is String || value is String) { /// await _preferences?.setString(key, value as String); /// } /// throw Exception('No Implementation Found'); /// } /// ``` /// Future setObject(String key, T? value); /// Method to get custom object value from the storage, if the retrieved value is null /// then [defaultValue] should be used, if provided. /// /// /// Example code(SharedPreferences Implementation): /// /// ```dart /// T? getValue(String key, {T? defaultValue}) { /// if (T is int || defaultValue is int) { /// return _preferences?.getInt(key) as T; /// } /// if (T is double || defaultValue is double) { /// return _preferences?.getDouble(key) as T; /// } /// if (T is bool || defaultValue is bool) { /// return _preferences?.getBool(key) as T; /// } /// if (T is String || defaultValue is String) { /// return _preferences?.getString(key) as T; /// } /// throw Exception('No Implementation Found'); /// } /// ``` /// /// T - Represents any valid class or primitive types supported by flutter /// /// The [defaultValue] is an optional & nullable value, you may choose not to provide it /// in which case the null value will be returned as result. /// /// Output can be represented like this: /// getterOutput -if-absent-> defaultValue -if-absent-> null /// /// here [getterOutput] is the expected output of the getter method called. /// The method must be implementation of one of the definitions from the cache provider /// T? getValue(String key, {T? defaultValue}); } ================================================ FILE: lib/src/cache/cache_provider_impl.dart ================================================ import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'cache_provider.dart'; /// A cache access provider class for shared preferences using shared_preferences library. /// /// This cache provider implementation is used by default, if none is provided explicitly. class SharePreferenceCache extends CacheProvider { SharedPreferences? _preferences; @override Future init() async { WidgetsFlutterBinding.ensureInitialized(); _preferences = await SharedPreferences.getInstance(); } Set get keys => getKeys(); @override bool? getBool(String key, {bool? defaultValue}) { return _preferences?.getBool(key); } @override double? getDouble(String key, {double? defaultValue}) { return _preferences?.getDouble(key); } @override int? getInt(String key, {int? defaultValue}) { return _preferences?.getInt(key); } @override String? getString(String key, {String? defaultValue}) { return _preferences?.getString(key); } @override Future setBool(String key, bool? value) async { await _preferences?.setBool(key, value ?? false); } @override Future setDouble(String key, double? value) async { await _preferences?.setDouble(key, value!); } @override Future setInt(String key, int? value) async { await _preferences?.setInt(key, value!); } @override Future setString(String key, String? value) async { await _preferences?.setString(key, value!); } @override Future setObject(String key, T? value) async { if (T == int || value is int) { await _preferences?.setInt(key, value as int); } else if (T == double || value is double) { await _preferences?.setDouble(key, value as double); } else if (T == bool || value is bool) { await _preferences?.setBool(key, value as bool); } else if (T == String || value is String) { await _preferences?.setString(key, value as String); } else { throw Exception( "flutter_settings_screens doesn't handle values of type $T"); } } @override bool containsKey(String key) { return _preferences?.containsKey(key) ?? false; } @override Set getKeys() { return _preferences?.getKeys() ?? {}; } @override Future remove(String key) async { if (containsKey(key)) { await _preferences?.remove(key); } } @override Future removeAll() async { await _preferences?.clear(); } @override T? getValue(String key, {T? defaultValue}) { if (T == int || defaultValue is int) { return _preferences?.getInt(key) as T; } if (T == double || defaultValue is double) { return _preferences?.getDouble(key) as T; } if (T == bool || defaultValue is bool) { return _preferences?.getBool(key) as T; } if (T == String || defaultValue is String) { return _preferences?.getString(key) as T; } throw Exception( "flutter_settings_screens doesn't handle values of type $T"); } } ================================================ FILE: lib/src/settings.dart ================================================ import 'package:flutter/material.dart'; import 'cache/cache.dart'; /// This function type is used for rebuilding any given child widgets /// /// All Settings Tile are given a [ValueChangeObserver] with a unique settings key. /// /// When the value associated with a settings key changes, [ValueChangeObserver] /// triggers a [InternalWidgetBuilder] function call. typedef InternalWidgetBuilder = Widget Function( BuildContext, T, ValueChanged); /// This function type is used for building a widget based on the value given to it. /// It is an alternate version of [WidgetBuilder]. typedef ItemBuilder = Widget Function(T); /// This function type is used for triggering the callback when value /// associated with a settings key changes. typedef OnChanged = void Function(T); /// This function type is used for verifying that the dialog/modal /// widget is to be closed or not. /// /// if true, the widget will be closed typedef OnConfirmedCallback = bool Function(); /// This class behaves as a bridge between the settings screen widgets and the /// underlying storage mechanism. /// /// To access the storage mechanism an instance of the [CacheProvider] implementation /// is required. /// /// Any Flutter App using this library must call `Settings.init(cacheProvider)` /// before showing the settings screen. /// /// class Settings { /// Private constructor Settings._internal(); /// Private instance of this class to keep it singleton static final Settings _instance = Settings._internal(); /// Public factory method to provide the factory Settings() { assert( _cacheProvider != null, 'Must call Settings.init(cacheProvider)' ' before using settings!'); return _instance; } /// Private instance of [CacheProvider] which will allow access to the /// underlying cache mechanism, which can be any of [SharedPreference],[Hive] /// or any other cache provider of choice static CacheProvider? _cacheProvider; /// This method will check and ensure that [_cacheProvider] /// value is set properly. static void ensureCacheProvider() { assert( isInitialized, 'Must call Settings.init(cacheProvider)' ' before using settings!'); } /// A getter to know if the settings are already initialized or not static bool get isInitialized => _cacheProvider != null; /// This method is used for initializing the [_cacheProvider] /// instance. /// /// This method must be called before the Settings screen is displayed. /// /// Cache provider is optional, default cache provider uses the /// shared preferences based cache provider implementation. static Future init({CacheProvider? cacheProvider}) async { cacheProvider ??= SharePreferenceCache(); _cacheProvider = cacheProvider; await _cacheProvider!.init(); } /// method to check if the cache provider contains given [key] or not. static bool? containsKey(String key) { ensureCacheProvider(); return _cacheProvider?.containsKey(key); } /// method to get a value using the [cacheProvider] for given [key] /// /// If there's no value found associated with the [key] then the [defaultValue] /// is returned. static T? getValue(String key, {T? defaultValue}) { ensureCacheProvider(); final containsKey = _cacheProvider?.containsKey(key); if (containsKey ?? false) { final prefValue = _cacheProvider?.getValue(key, defaultValue: defaultValue); return prefValue ?? defaultValue; } return defaultValue; } /// method to set [value] using the [cacheProvider] for given [key] static Future setValue( String key, T value, { bool notify = false, }) async { ensureCacheProvider(); if (value == null) { return _cacheProvider?.remove(key); } await _cacheProvider?.setObject(key, value); if (notify) { _notifyGlobally(key, value); } } /// method to clear all the cached data using the [cacheProvider] static void clearCache() { ensureCacheProvider(); _cacheProvider?.removeAll(); } } /// This class is a customized version of [ValueNotifier] /// which Takes a [key] and [value]. /// /// Value - Setting value which is associated to the [key] class ValueChangeNotifier extends ValueNotifier { /// A String which represents a setting (assumed to be unique) final String key; ValueChangeNotifier(this.key, value) : super(value); @override set value(T newValue) { Settings.setValue(key, newValue); super.value = newValue; _notifyGlobally(key, newValue); } @override void notifyListeners() { super.notifyListeners(); } @override String toString() { return '\n{VCN: \n\tkey: $key \n\tvalue: $value\n}'; } } void _notifyGlobally(String key, T newValue) { final notifiers = _fetchNotifiersForKey(key); if (notifiers == null || notifiers.isEmpty) return; for (var notifier in notifiers) { final currentValue = notifier.value; if (currentValue != newValue) { notifier.value = newValue; // ignore: avoid_print print(': _notifyGlobally: updating $key notifier'); } } } List? _fetchNotifiersForKey(String key) { final finalKey = key.toLowerCase().trim(); return _notifiers[finalKey]; } /// This map is used for keeping the track of the notifier(s) associated with /// a Settings key /// /// If a settings key is already added in the map, the new notifier /// is added to the list of notifiers Map> _notifiers = >{}; /// A Stateful widget which Takes in a [cacheKey], a [defaultValue] /// and a [builder] /// /// This widget rebuilds whenever there's a change in value associated with the /// [cacheKey] class ValueChangeObserver extends StatefulWidget { final String cacheKey; final T defaultValue; final InternalWidgetBuilder builder; const ValueChangeObserver({ super.key, required this.cacheKey, required this.defaultValue, required this.builder, }); @override State> createState() => _ValueChangeObserverState(); } class _ValueChangeObserverState extends State> { T? value; String get cacheKey => widget.cacheKey; T get defaultValue => widget.defaultValue; late ValueChangeNotifier notifier; @override void dispose() { if (!mounted) { _notifiers.remove(cacheKey); } super.dispose(); } @override void initState() { //if [cacheKey] is not found, add new cache in the [cacheProvider] with [defaultValue] final containsKey = Settings.containsKey(cacheKey) ?? false; if (!containsKey) { Settings.setValue(cacheKey, defaultValue); } // get cache value from the [cacheProvider] value = Settings.getValue(cacheKey, defaultValue: defaultValue); // assign a notifier notifier = ValueChangeNotifier(cacheKey, value); // add notifier to [_notifiers] map if (!_notifiers.containsKey(cacheKey)) { _notifiers[cacheKey] = List>.empty(growable: true); } _notifiers[cacheKey]?.add(notifier); super.initState(); } @override Widget build(BuildContext context) { return ValueListenableBuilder( valueListenable: notifier, builder: (BuildContext context, T value, Widget? child) { return widget.builder(context, value, onChange); }, ); } /// This method is used to trigger all the associated notifiers /// when associated value is changed in cache void onChange(T newValue) { _notifiers[cacheKey]?.forEach((notifier) { notifier.value = newValue; }); } } ================================================ FILE: lib/src/utils/utils.dart ================================================ import 'dart:ui'; class ConversionUtils { static Color colorFromString(String colorString) { colorString = colorString.replaceFirst('#', ''); colorString = colorString.length == 6 ? 'ff$colorString' : colorString; final colorHexInt = int.parse(colorString, radix: 16); return Color(colorHexInt); } static int intFromString(String value) { const alphaString = 'ff000000'; return int.parse(value, radix: 16) & int.parse(alphaString, radix: 16); } static String stringFromColor(Color color) { return '#${color.value.toRadixString(16)}'; } } ================================================ FILE: lib/src/utils/widget_utils.dart ================================================ import 'package:flutter/material.dart'; /// A method that will add default leading padding to all children in the list List getPaddedParentChildrenList(List childrenIfEnabled) { return childrenIfEnabled.map((childWidget) { return Padding( padding: const EdgeInsets.only(left: 8.0), child: childWidget, ); }).toList(); } TextStyle? headerTextStyle(BuildContext context) => Theme.of(context).textTheme.titleLarge?.copyWith(fontSize: 16.0); TextStyle? subtitleTextStyle(BuildContext context) => Theme.of(context) .textTheme .titleSmall ?.copyWith(fontSize: 13.0, fontWeight: FontWeight.normal); ================================================ FILE: lib/src/widgets/base_widgets.dart ================================================ part of 'settings_widgets.dart'; /// [SettingsScreen] is a simple Screen widget that may contain Tiles or other /// widgets. /// /// * Example: /// ```dart /// SettingsScreen( /// title: 'Application Settings', /// children: [ /// SettingsGroup( /// title: 'Single Choice Settings', /// children: [ /// SwitchSettingsTile( /// settingKey: 'key-wifi', /// title: 'Wi-Fi', /// enabledLabel: 'Enabled', /// disabledLabel: 'Disabled', /// leading: Icon(Icons.wifi), /// onChange: (value) { /// debugPrint('key-wifi: $value'); /// }, /// ), /// CheckboxSettingsTile( /// settingKey: 'key-blue-tooth', /// title: 'Bluetooth', /// enabledLabel: 'Enabled', /// disabledLabel: 'Disabled', /// leading: Icon(Icons.bluetooth), /// onChange: (value) { /// debugPrint('key-blue-tooth: $value'); /// }, /// ), /// SwitchSettingsTile( /// leading: Icon(Icons.developer_mode), /// settingKey: 'key-switch-dev-mode', /// title: 'Developer Settings', /// onChange: (value) { /// debugPrint('key-switch-dev-mod: $value'); /// }, /// childrenIfEnabled: [ /// CheckboxSettingsTile( /// leading: Icon(Icons.adb), /// settingKey: 'key-is-developer', /// title: 'Developer Mode', /// onChange: (value) { /// debugPrint('key-is-developer: $value'); /// }, /// ), /// SwitchSettingsTile( /// leading: Icon(Icons.usb), /// settingKey: 'key-is-usb-debugging', /// title: 'USB Debugging', /// onChange: (value) { /// debugPrint('key-is-usb-debugging: $value'); /// }, /// ), /// SimpleSettingsTile( /// title: 'Root Settings', /// subtitle: 'These settings is not accessible', /// enabled: false, /// ), /// ], /// ), /// ], /// ), /// ], /// ); /// ``` class SettingsScreen extends StatelessWidget { /// Appbar title in Scaffold. final String title; /// Content of the screen, body of the Scaffold. final List children; final bool hasAppBar; const SettingsScreen({ super.key, required this.children, this.hasAppBar = true, this.title = 'Settings', }); @override Widget build(BuildContext context) { return Scaffold( appBar: hasAppBar ? AppBar( title: Text(title), ) : null, body: ListView.builder( shrinkWrap: true, itemCount: children.length, itemBuilder: (BuildContext context, int index) { return children[index]; }, ), ); } } /// [_SettingsTile] is a Basic Building block for Any Settings widget. /// /// This widget is container for any widget which is to be used for setting. class _SettingsTile extends StatefulWidget { /// title string for the tile final String title; /// widget to be placed at first in the tile final Widget? leading; /// subtitle string for the tile final String? subtitle; /// title text style final TextStyle? titleTextStyle; /// subtitle text style final TextStyle? subtitleTextStyle; /// flag to represent if the tile is accessible or not, if false user input is ignored final bool enabled; /// widget which is placed as the main element of the tile as settings UI final Widget child; /// call back for handling the tap event on tile final GestureTapCallback? onTap; // /// flag to show the child below the main tile elements // final bool showChildBelow; final bool showDivider; const _SettingsTile({ required this.title, required this.child, this.subtitle = '', this.titleTextStyle, this.subtitleTextStyle, this.onTap, this.enabled = true, // this.showChildBelow = false, this.leading, this.showDivider = true, }); @override __SettingsTileState createState() => __SettingsTileState(); } class __SettingsTileState extends State<_SettingsTile> { @override void initState() { super.initState(); } @override Widget build(BuildContext context) { return Material( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ ListTile( leading: widget.leading, title: Text( widget.title, style: widget.titleTextStyle ?? headerTextStyle(context), ), subtitle: widget.subtitle?.isEmpty ?? true ? null : Text( widget.subtitle!, style: widget.subtitleTextStyle ?? subtitleTextStyle(context), ), enabled: widget.enabled, onTap: widget.onTap, // trailing: Visibility( // visible: !widget.showChildBelow, // child: widget.child, // ), trailing: widget.child, dense: true, // wrap only if the subtitle is longer than 70 characters isThreeLine: (widget.subtitle?.isNotEmpty ?? false) && widget.subtitle!.length > 70, ), // Visibility( // visible: widget.showChildBelow, // child: widget.child, // ), if (widget.showDivider) _SettingsTileDivider(), ], ), ); } } /// [_SimpleHeaderTile] is a widget which is used to show Leading, Title and subtitle /// of a [_SettingsTile] without the main child widget class _SimpleHeaderTile extends StatefulWidget { /// title string for the tile final String? title; /// subtitle string for the tile final String? subtitle; /// title text style final TextStyle? titleTextStyle; /// subtitle text style final TextStyle? subtitleTextStyle; /// widget to be placed at first in the tile final Widget? leading; const _SimpleHeaderTile({ this.title, this.subtitle = '', this.leading, this.titleTextStyle, this.subtitleTextStyle, }); @override __SimpleHeaderTileState createState() => __SimpleHeaderTileState(); } class __SimpleHeaderTileState extends State<_SimpleHeaderTile> { @override Widget build(BuildContext context) { return AbsorbPointer( child: ListTile( title: Text( widget.title ?? '', style: widget.titleTextStyle ?? headerTextStyle(context), ), subtitle: (widget.subtitle?.isNotEmpty ?? false) ? Text( widget.subtitle!, style: widget.subtitleTextStyle ?? subtitleTextStyle(context), ) : null, leading: widget.leading, ), ); } } /// [_ExpansionSettingsTile] is a special setting widget which has two states. /// /// Collapsed State: /// In this state the settings tile would only show the Title,Subtitle and leading /// widget /// /// Expanded State: /// In this state the settings tile would show all widgets in collapsed state, /// but also the children widgets. class _ExpansionSettingsTile extends StatefulWidget { /// title string for the tile final String title; /// subtitle string for the tile, default = '' final String subtitle; /// title text style final TextStyle? titleTextStyle; /// subtitle text style final TextStyle? subtitleTextStyle; /// flag to represent if the tile is accessible or not, if false user input is ignored /// default = true final bool enabled; /// flag to represent the current state of the expansion tile, if true it means that /// the tile is in expanded mode /// default = false final bool expanded; /// The widget displayed when the tile is expanded final Widget child; /// The widget shown in front of the title final Widget? leading; /// A Callback for the change of the Expansion state final Function(bool)? onExpansionChanged; final bool showDivider; const _ExpansionSettingsTile({ required this.title, required this.child, this.subtitle = '', this.titleTextStyle, this.subtitleTextStyle, this.enabled = true, this.expanded = false, this.onExpansionChanged, this.leading, this.showDivider = true, }); @override _ExpansionSettingsTileState createState() => _ExpansionSettingsTileState(); } class _ExpansionSettingsTileState extends State<_ExpansionSettingsTile> { @override void initState() { super.initState(); } @override Widget build(BuildContext context) { return widget.enabled ? getExpansionTile() : getListTile(); } Widget getListTile() { return _SettingsTile( title: widget.title, subtitle: widget.subtitle, enabled: false, leading: widget.leading, showDivider: widget.showDivider, child: const Text(''), ); } Widget getExpansionTile() { return Material( child: ExpansionTile( title: Text( widget.title, style: widget.titleTextStyle ?? headerTextStyle(context), ), leading: widget.leading, subtitle: Text( widget.subtitle, style: widget.subtitleTextStyle ?? subtitleTextStyle(context), ), initiallyExpanded: widget.expanded, childrenPadding: const EdgeInsets.only(left: 8.0), children: [widget.child], ), ); } } ///[_ModalSettingsTile] is a widget which shows the given child widget inside a /// dialog view. /// /// This widget can be used to show a settings UI which is too big for a single /// tile in the SettingScreen UI or a Setting tile which needs to be shown separately. class _ModalSettingsTile extends StatefulWidget { /// title string for the tile final String title; /// subtitle string for the tile, default = '' final String? subtitle; /// title text style final TextStyle? titleTextStyle; /// subtitle text style final TextStyle? subtitleTextStyle; /// flag to represent if the tile is accessible or not, if false user input is ignored /// default = true final bool enabled; /// The widget shown in front of the title final Widget? leading; /// The list widgets which will be displayed in a vertical list manner /// when the dialog is displayed final List children; /// flag that determines if the dialog will be displayed with /// confirmation buttons or not . /// Buttons like, ok & cancel /// /// default = false final bool showConfirmation; /// Callback to execute when user taps cancel button, /// It is a simple void callback to execute when user wants to revert the changes /// back to previous /// /// **Note**: the action performed will not affect the settings that were updated /// automatically. However you can choose to modify them as per your need by referencing /// the values from the callback & updating final VoidCallback? onCancel; /// Callback to execute when user taps ok button, while [onCancel] callback /// is a simple void callback, this one allows you to perform some task /// before closing the dialog. /// /// **Note**: the action performed will not affect the settings that were updated /// automatically. However you can choose to modify them as per your need by referencing /// the values from the callback & updating final OnConfirmedCallback? onConfirm; const _ModalSettingsTile({ required this.title, required this.children, this.subtitle = '', this.enabled = true, this.leading, this.showConfirmation = false, this.onCancel, this.onConfirm, this.titleTextStyle, this.subtitleTextStyle, }); @override __ModalSettingsTileState createState() => __ModalSettingsTileState(); } class __ModalSettingsTileState extends State<_ModalSettingsTile> { @override void initState() { super.initState(); } @override Widget build(BuildContext context) { return Material( child: ListTile( leading: widget.leading, title: Text(widget.title, style: widget.titleTextStyle ?? headerTextStyle(context)), subtitle: Text( widget.subtitle!, style: widget.subtitleTextStyle ?? subtitleTextStyle(context), ), enabled: widget.enabled, onTap: () => _showWidget(context, widget.children), dense: true, ), ); } void _showWidget(BuildContext context, List children) { showDialog( context: context, builder: (dialogContext) { return SimpleDialog( title: Center( child: getTitle(), ), titlePadding: const EdgeInsets.fromLTRB(8.0, 10.0, 8.0, 10.0), contentPadding: EdgeInsets.zero, children: _finalWidgets(dialogContext, children), ); }); } List _finalWidgets( BuildContext dialogContext, List children) { if (!widget.showConfirmation) { return children; } return _addActionWidgets(dialogContext, children); } Widget getTitle() { return widget.leading != null ? Row( children: [ widget.leading!, Text(widget.title, style: headerTextStyle(context)), ], ) : Text(widget.title, style: headerTextStyle(context)); } List _addActionWidgets( BuildContext dialogContext, List children) { final finalList = List.from(children); finalList.add(ButtonBar( alignment: MainAxisAlignment.end, children: [ TextButton( style: TextButton.styleFrom( padding: EdgeInsets.zero, ), onPressed: () { widget.onCancel?.call(); _disposeDialog(dialogContext); }, child: Text(MaterialLocalizations.of(dialogContext).cancelButtonLabel), ), TextButton( style: TextButton.styleFrom( padding: EdgeInsets.zero, ), onPressed: () async { var closeDialog = true; if (widget.onConfirm != null) { closeDialog = widget.onConfirm!.call(); } if (closeDialog) { _disposeDialog(dialogContext); } }, child: Text(MaterialLocalizations.of(dialogContext).okButtonLabel), ) ], )); return finalList; } void _disposeDialog(BuildContext dialogContext) { Navigator.of(dialogContext).pop(); } } /// [_SettingsTileDivider] is widget which is used as a Divide various settings /// tile in a list class _SettingsTileDivider extends StatelessWidget { @override Widget build(BuildContext context) { return const Divider( height: 0.0, ); } } /// [_SettingsCheckbox] is a Settings UI version of the [Checkbox] widget. class _SettingsCheckbox extends StatelessWidget { /// current state of the checkbox final bool value; /// on change callback to handle state change final OnChanged onChanged; /// flag which represents the state of the settings, if false the the tile will /// ignore all the user inputs final bool enabled; const _SettingsCheckbox({ required this.value, required this.onChanged, required this.enabled, }); @override Widget build(BuildContext context) { return Checkbox( value: value, onChanged: enabled ? onChanged : null, activeColor: Theme.of(context).colorScheme.secondary, ); } } /// [_SettingsSwitch] is a Settings UI version of the [Switch] widget class _SettingsSwitch extends StatelessWidget { /// current state of the switch final bool value; /// on change callback to handle state change final OnChanged onChanged; /// flag which represents the state of the settings, if false the the tile will /// ignore all the user inputs final bool enabled; final Color? activeColor; const _SettingsSwitch({ required this.value, required this.onChanged, required this.enabled, required this.activeColor, }); @override Widget build(BuildContext context) { return Switch.adaptive( value: value, onChanged: enabled ? onChanged : null, activeColor: activeColor ?? Theme.of(context).colorScheme.secondary, ); } } /// [_SettingsRadio] is a Settings UI version of the [Radio] widgets class _SettingsRadio extends StatelessWidget { /// value of the selected radio in this group final T groupValue; /// value of the current radio widget final T value; /// on change callback to handle state change final OnChanged onChanged; /// flag which represents the state of the settings, if false the the tile will /// ignore all the user inputs final bool enabled; final Color? activeColor; const _SettingsRadio({ required this.groupValue, required this.value, required this.onChanged, required this.enabled, required this.activeColor, }); @override Widget build(BuildContext context) { return Radio( groupValue: groupValue, value: value, onChanged: enabled ? onChanged : null, activeColor: activeColor, ); } } /// [_SettingsDropDown] is a Settings UI version of the [DropdownButton] class _SettingsDropDown extends StatelessWidget { /// value of the selected in this dropdown final T selected; /// Alignment of the dropdown. Defaults to [AlignmentDirectional.centerEnd]. final AlignmentGeometry alignment; /// List of values for this dropdown final List values; /// on change call back to handle selected value change final OnChanged onChanged; /// single item builder for creating a [DropdownMenuItem] final ItemBuilder itemBuilder; /// flag which represents the state of the settings, if false the the tile will /// ignore all the user inputs final bool enabled; const _SettingsDropDown({ required this.selected, required this.values, required this.onChanged, required this.itemBuilder, this.alignment = AlignmentDirectional.centerEnd, this.enabled = true, }); @override Widget build(BuildContext context) { return Wrap( alignment: WrapAlignment.end, children: [ DropdownButton( isDense: true, value: selected, alignment: alignment, onChanged: enabled ? onChanged : null, underline: Container(), items: values.map>( (T val) { return DropdownMenuItem( value: val, child: itemBuilder(val), ); }, ).toList(), ), ], ); } } /// [_SettingsSlider] is a Settings UI version of [Slider] widget class _SettingsSlider extends StatelessWidget { /// min value allowed for the slider final double min; /// max value allowed for the slider final double max; /// step value for slider interval final double step; /// current value of the slider final double value; /// on change callback to handle the value change when slider starts moving final OnChanged? onChangeStart; /// on change callback to handle the value change final OnChanged? onChanged; /// on change callback to handle the value change when slider stops moving final OnChanged? onChangeEnd; /// flag which represents the state of the settings, if false then the tile will /// ignore all the user inputs final bool enabled; /// flag which allows updating the value of setting immediately when the /// slider is moved, default = true /// /// If this flag is enabled then [onChangeStart] & [onChangeEnd] callbacks are /// ignored & will not be executed final bool eagerUpdate; final Color? activeColor; final Color? inActiveColor; const _SettingsSlider({ required this.value, required this.min, required this.max, required this.step, required this.enabled, required this.activeColor, required this.inActiveColor, this.onChangeStart, this.onChanged, this.onChangeEnd, this.eagerUpdate = true, }); @override Widget build(BuildContext context) { return Slider.adaptive( value: value, min: min, max: max, divisions: (max - min) ~/ (step), onChangeStart: enabled && !eagerUpdate ? (value) => onChangeStart?.call(value) : null, onChanged: enabled ? (value) => onChanged?.call(value) : null, onChangeEnd: enabled && !eagerUpdate ? (value) => onChangeEnd?.call(value) : null, activeColor: activeColor, inactiveColor: inActiveColor, ); } } /// [_SettingsColorPicker] is a widget which allows picking colors /// from pallet of colors class _SettingsColorPicker extends StatelessWidget { /// title of the settings tile and color pallet dialog final String title; final String subtitle; /// title text style final TextStyle? titleTextStyle; /// subtitle text style final TextStyle? subtitleTextStyle; /// The widget shown in front of the title final Widget? leading; /// current value of the slider final String value; /// on change callback to handle the value change final OnChanged onChanged; /// flag which represents the state of the settings, if false the the tile will /// ignore all the user inputs final bool enabled; final bool showDivider; const _SettingsColorPicker({ required this.value, required this.onChanged, required this.enabled, required this.title, this.subtitle = '', this.leading, this.titleTextStyle, this.subtitleTextStyle, this.showDivider = true, }); @override Widget build(BuildContext context) { return _SettingsTile( title: title, subtitle: subtitle.isNotEmpty ? subtitle : value, leading: leading, enabled: enabled, onTap: () => _showColorPicker(context, value), titleTextStyle: titleTextStyle, subtitleTextStyle: subtitleTextStyle, showDivider: showDivider, child: FloatingActionButton( heroTag: null, backgroundColor: ConversionUtils.colorFromString(value), elevation: 0, onPressed: enabled ? () => _showColorPicker(context, value) : null, ), ); } void _showColorPicker(BuildContext context, String value) { Widget dialogContent = MaterialColorPicker( shrinkWrap: true, selectedColor: ConversionUtils.colorFromString(value), onColorChange: (Color? color) { if (color == null) return; onChanged(ConversionUtils.stringFromColor(color)); }, ); showDialog( context: context, builder: (dialogContext) { return AlertDialog( title: const Text('Pick a Color'), content: dialogContent, ); }, ); } } ================================================ FILE: lib/src/widgets/color_picker/circle_color.dart ================================================ import 'package:flutter/material.dart'; class CircleColor extends StatelessWidget { static const double _kColorElevation = 4.0; final bool isSelected; final Color color; final VoidCallback? onColorChoose; final double circleSize; final double? elevation; final IconData? iconSelected; const CircleColor({ super.key, required this.color, required this.circleSize, this.onColorChoose, this.isSelected = false, this.elevation = _kColorElevation, this.iconSelected, }) : assert(!isSelected || (isSelected && iconSelected != null)); @override Widget build(BuildContext context) { final brightness = ThemeData.estimateBrightnessForColor(color); final icon = brightness == Brightness.light ? Colors.black : Colors.white; return GestureDetector( onTap: onColorChoose, child: Material( elevation: elevation ?? _kColorElevation, shape: const CircleBorder(), child: CircleAvatar( radius: circleSize / 2, backgroundColor: color, child: isSelected ? Icon(iconSelected, color: icon) : null, ), ), ); } } ================================================ FILE: lib/src/widgets/color_picker/colors.dart ================================================ import 'package:flutter/material.dart'; const List materialColors = [ Colors.red, Colors.pink, Colors.purple, Colors.deepPurple, Colors.indigo, Colors.blue, Colors.lightBlue, Colors.cyan, Colors.teal, Colors.green, Colors.lightGreen, Colors.lime, Colors.yellow, Colors.amber, Colors.orange, Colors.deepOrange, Colors.brown, Colors.grey, Colors.blueGrey ]; const List accentColors = [ Colors.redAccent, Colors.pinkAccent, Colors.purpleAccent, Colors.deepPurpleAccent, Colors.indigoAccent, Colors.blueAccent, Colors.lightBlueAccent, Colors.cyanAccent, Colors.tealAccent, Colors.greenAccent, Colors.lightGreenAccent, Colors.limeAccent, Colors.yellowAccent, Colors.amberAccent, Colors.orangeAccent, Colors.deepOrangeAccent, ]; const List fullMaterialColors = [ ColorSwatch(0xFFFFFFFF, {500: Colors.white}), ColorSwatch(0xFF000000, {500: Colors.black}), Colors.red, Colors.redAccent, Colors.pink, Colors.pinkAccent, Colors.purple, Colors.purpleAccent, Colors.deepPurple, Colors.deepPurpleAccent, Colors.indigo, Colors.indigoAccent, Colors.blue, Colors.blueAccent, Colors.lightBlue, Colors.lightBlueAccent, Colors.cyan, Colors.cyanAccent, Colors.teal, Colors.tealAccent, Colors.green, Colors.greenAccent, Colors.lightGreen, Colors.lightGreenAccent, Colors.lime, Colors.limeAccent, Colors.yellow, Colors.yellowAccent, Colors.amber, Colors.amberAccent, Colors.orange, Colors.orangeAccent, Colors.deepOrange, Colors.deepOrangeAccent, Colors.brown, Colors.grey, Colors.blueGrey ]; ================================================ FILE: lib/src/widgets/color_picker/material_color_picker.dart ================================================ import 'package:flutter/material.dart'; import 'circle_color.dart'; import 'colors.dart'; class MaterialColorPicker extends StatefulWidget { final Color? selectedColor; final ValueChanged? onColorChange; final ValueChanged? onMainColorChange; final List? colors; final bool shrinkWrap; final ScrollPhysics? physics; final bool allowShades; final bool onlyShadeSelection; final double circleSize; final double spacing; final IconData iconSelected; final VoidCallback? onBack; final double? elevation; const MaterialColorPicker({ super.key, this.selectedColor, this.onColorChange, this.onMainColorChange, this.colors, this.shrinkWrap = false, this.physics, this.allowShades = true, this.onlyShadeSelection = false, this.iconSelected = Icons.check, this.circleSize = 45.0, this.spacing = 9.0, this.onBack, this.elevation, }); @override State createState() => _MaterialColorPickerState(); } class _MaterialColorPickerState extends State { final _defaultValue = materialColors[0]; List? _colors = materialColors; ColorSwatch? _mainColor; Color? _shadeColor; late bool _isMainSelection; @override void initState() { super.initState(); _initSelectedValue(); } @protected @override void didUpdateWidget(covariant MaterialColorPicker oldWidget) { _initSelectedValue(); super.didUpdateWidget(oldWidget); } void _initSelectedValue() { if (widget.colors != null) _colors = widget.colors; Color? shadeColor = widget.selectedColor ?? _defaultValue; var mainColor = _findMainColor(shadeColor); if (mainColor == null) { mainColor = _colors![0]; shadeColor = mainColor[500] ?? mainColor[400]!; } setState(() { _mainColor = mainColor; _shadeColor = shadeColor; _isMainSelection = true; }); } ColorSwatch? _findMainColor(Color? shadeColor) { for (final mainColor in _colors!) { if (_isShadeOfMain(mainColor, shadeColor)) return mainColor; } return (shadeColor is ColorSwatch && _colors!.contains(shadeColor)) ? shadeColor : null; } bool _isShadeOfMain(ColorSwatch mainColor, Color? shadeColor) { for (final shade in _getMaterialColorShades(mainColor)) { if (shade == shadeColor) return true; } return false; } void _onMainColorSelected(ColorSwatch color) { var isShadeOfMain = _isShadeOfMain(color, _shadeColor); final shadeColor = isShadeOfMain ? _shadeColor : (color[500] ?? color[400]); setState(() { _mainColor = color; _shadeColor = shadeColor; _isMainSelection = false; }); if (widget.onMainColorChange != null) widget.onMainColorChange!(color); if (widget.onlyShadeSelection && !_isMainSelection) { return; } if (widget.allowShades && widget.onColorChange != null) { widget.onColorChange!(shadeColor); } } void _onShadeColorSelected(Color? color) { setState(() => _shadeColor = color); if (widget.onColorChange != null) widget.onColorChange!(color); } void _onBack() { setState(() => _isMainSelection = true); if (widget.onBack != null) widget.onBack!(); } List _buildListMainColor(List colors) { return [ for (final color in colors) CircleColor( color: color, circleSize: widget.circleSize, onColorChoose: () => _onMainColorSelected(color), isSelected: _mainColor == color, iconSelected: widget.iconSelected, elevation: widget.elevation, ) ]; } List _getMaterialColorShades(ColorSwatch color) { return [ if (color[50] != null) color[50], if (color[100] != null) color[100], if (color[200] != null) color[200], if (color[300] != null) color[300], if (color[400] != null) color[400], if (color[500] != null) color[500], if (color[600] != null) color[600], if (color[700] != null) color[700], if (color[800] != null) color[800], if (color[900] != null) color[900], ]; } List _buildListShadesColor(ColorSwatch color) { return [ IconButton( icon: const Icon(Icons.arrow_back), onPressed: _onBack, padding: const EdgeInsets.only(right: 2.0), ), for (final color in _getMaterialColorShades(color)) CircleColor( color: color!, circleSize: widget.circleSize, onColorChoose: () => _onShadeColorSelected(color), isSelected: _shadeColor == color, iconSelected: widget.iconSelected, elevation: widget.elevation, ), ]; } @override Widget build(BuildContext context) { final listChildren = _isMainSelection || !widget.allowShades ? _buildListMainColor(_colors!) : _buildListShadesColor(_mainColor!); // Size of dialog final width = MediaQuery.of(context).size.width * .80; // Number of circle per line, depend on width and circleSize final nbrCircleLine = width ~/ (widget.circleSize + widget.spacing); return SizedBox( width: width, child: GridView.count( shrinkWrap: widget.shrinkWrap, physics: widget.physics, padding: const EdgeInsets.all(16.0), crossAxisSpacing: widget.spacing, mainAxisSpacing: widget.spacing, crossAxisCount: nbrCircleLine, children: listChildren, ), ); } } ================================================ FILE: lib/src/widgets/settings_widgets.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_settings_screens/flutter_settings_screens.dart'; import 'package:flutter_settings_screens/src/utils/widget_utils.dart'; import 'color_picker/material_color_picker.dart'; part 'base_widgets.dart'; /// --------- Types of Settings widgets ---------- /// /// [SimpleSettingsTile] is a simple settings tile that can open a new screen /// by tapping the tile. /// /// Example: /// ```dart /// SimpleSettingsTile( /// title: 'Advanced', /// subtitle: 'More, advanced settings.' /// child: SettingsScreen( /// title: 'Sub menu', /// children: [ /// CheckboxSettingsTile( /// settingsKey: 'key-of-your-setting', /// title: 'This is a simple Checkbox', /// ), /// ], /// ), /// ); /// ``` class SimpleSettingsTile extends StatelessWidget { /// title string for the tile final String title; /// subtitle string for the tile final String? subtitle; /// title text style final TextStyle? titleTextStyle; /// subtitle text style final TextStyle? subtitleTextStyle; /// widget to be placed at first in the tile final Widget? leading; /// flag which represents the state of the settings, if false the the tile will /// ignore all the user inputs, default = true final bool enabled; /// widget that will be displayed on tap of the tile final Widget? child; final VoidCallback? onTap; final bool showDivider; const SimpleSettingsTile({ super.key, required this.title, this.subtitle, this.titleTextStyle, this.subtitleTextStyle, this.child, this.enabled = true, this.leading, this.onTap, this.showDivider = true, }); @override Widget build(BuildContext context) { return _SettingsTile( leading: leading, title: title, subtitle: subtitle, titleTextStyle: titleTextStyle, subtitleTextStyle: subtitleTextStyle, enabled: enabled, onTap: () => _handleTap(context), showDivider: showDivider, child: child != null ? getIcon(context) : const Text(''), ); } Widget getIcon(BuildContext context) { return IconButton( icon: const Icon(Icons.navigate_next), onPressed: enabled ? () => _handleTap(context) : null, ); } void _handleTap(BuildContext context) { onTap?.call(); if (child != null) { Navigator.of(context).push(MaterialPageRoute( builder: (BuildContext context) => child!, )); } } } /// [ModalSettingsTile] is a widget which allows creating /// a setting which shows the [children] in a [_ModalSettingsTile] /// /// Example: /// ```dart /// ModalSettingsTile( /// title: 'Quick setting dialog', /// subtitle: 'Settings on a dialog', /// children: [ /// CheckboxSettingsTile( /// settingKey: 'key-day-light-savings', /// title: 'Daylight Time Saving', /// enabledLabel: 'Enabled', /// disabledLabel: 'Disabled', /// leading: Icon(Icons.timelapse), /// ), /// SwitchSettingsTile( /// settingKey: 'key-dark-mode', /// title: 'Dark Mode', /// enabledLabel: 'Enabled', /// disabledLabel: 'Disabled', /// leading: Icon(Icons.palette), /// ), /// ], /// ); /// /// ``` /// class ModalSettingsTile extends StatelessWidget { /// title string for the tile final String title; /// subtitle string for the tile final String subtitle; /// title text style final TextStyle? titleTextStyle; /// subtitle text style final TextStyle? subtitleTextStyle; /// widget to be placed at first in the tile final Widget? leading; /// flag which represents the state of the settings, if false the the tile will /// ignore all the user inputs, default = true final bool enabled; /// List of widgets which are to be shown in the modal dialog final List children; final bool showConfirmation; final VoidCallback? onCancel; final OnConfirmedCallback? onConfirm; const ModalSettingsTile({ super.key, required this.title, required this.children, this.subtitle = '', this.enabled = true, this.leading, this.showConfirmation = false, this.onCancel, this.onConfirm, this.titleTextStyle, this.subtitleTextStyle, }); @override Widget build(BuildContext context) { return _ModalSettingsTile( leading: leading, title: title, subtitle: subtitle, titleTextStyle: titleTextStyle, subtitleTextStyle: subtitleTextStyle, enabled: enabled, onCancel: onCancel, onConfirm: onConfirm, showConfirmation: showConfirmation, children: children, ); } } /// [ExpandableSettingsTile] is wrapper widget which shows the /// given [children] in an [_ExpansionSettingsTile] /// /// Example: /// ```dart /// ExpandableSettingsTile( /// title: 'Quick setting dialog2', /// subtitle: 'Expandable Settings', /// onExpansionChanged: (state) { /// _bolusExpanded = state; /// }, /// children: [ /// CheckboxSettingsTile( /// settingKey: 'key-day-light-savings', /// title: 'Daylight Time Saving', /// enabledLabel: 'Enabled', /// disabledLabel: 'Disabled', /// leading: Icon(Icons.timelapse), /// ), /// SwitchSettingsTile( /// settingKey: 'key-dark-mode', /// title: 'Dark Mode', /// enabledLabel: 'Enabled', /// disabledLabel: 'Disabled', /// leading: Icon(Icons.palette), /// ), /// ], /// ); /// /// ``` class ExpandableSettingsTile extends StatelessWidget { /// title string for the tile final String title; /// subtitle string for the tile final String subtitle; /// title text style final TextStyle? titleTextStyle; /// subtitle text style final TextStyle? subtitleTextStyle; /// widget to be placed at first in the tile final Widget? leading; /// flag which represents the state of the settings, if false the the tile will /// ignore all the user inputs, default = true final bool enabled; /// List of the widgets which are to be shown when tile is expanded final List children; /// flag which represents the initial state of the tile, if true the tile state is /// set to expanded initially, default = false final bool expanded; /// A Callback for the change of the Expansion state final Function(bool)? onExpansionChanged; final bool showDivider; const ExpandableSettingsTile({ super.key, required this.title, required this.children, this.subtitle = '', this.enabled = true, this.expanded = false, this.onExpansionChanged, this.leading, this.titleTextStyle, this.subtitleTextStyle, this.showDivider = true, }); @override Widget build(BuildContext context) { return _ExpansionSettingsTile( leading: leading, title: title, subtitle: subtitle, titleTextStyle: titleTextStyle, subtitleTextStyle: subtitleTextStyle, enabled: enabled, expanded: expanded, onExpansionChanged: onExpansionChanged, showDivider: showDivider, child: SettingsContainer( children: children, ), ); } } /// [SettingsContainer] is a widget that helps its child or children to fit in /// the settings screen. It is helpful if you want to place other widgets than /// settings tiles in the settings screen body. /// Example: /// ```dart /// SettingsContainer( /// children: [ /// Text('First line'), /// Text('Second line'), /// Icon(Icons.palette), /// ], /// ); /// ``` class SettingsContainer extends StatelessWidget { /// List of widgets which will be shown as Custom list of setting tile final List children; /// flag to represent if this Container allows internal scrolling of the widgets /// along with the outer settings screen/container, /// if true, the list of widget inside are made scrollable, default = true final bool allowScrollInternally; final double leftPadding; const SettingsContainer({ super.key, required this.children, this.allowScrollInternally = false, this.leftPadding = 0.0, }); @override Widget build(BuildContext context) { return _buildChild(); } Widget _buildChild() { var child = allowScrollInternally ? getList(children) : getColumn(children); return Padding( padding: const EdgeInsets.only( top: 0.0, ), child: Material( child: Container( padding: EdgeInsets.only(left: leftPadding), child: child, ), ), ); } Widget getList(List children) { return ListView.builder( itemBuilder: (context, index) { return children[index]; }, shrinkWrap: true, itemCount: children.length, ); } Widget getColumn(List children) { return Column( mainAxisSize: MainAxisSize.max, crossAxisAlignment: CrossAxisAlignment.stretch, children: children, ); } } /// [SettingsGroup] is a widget that contains multiple settings tiles and other /// widgets together as a group and shows a title/name of that group. /// /// All the children widget will have a small padding from the left and top /// to provide a sense that they in a separate group from others /// /// Example: /// /// ```dart /// SettingsGroup( /// title: 'Group title', /// children: [ /// CheckboxSettingsTile( /// settingKey: 'key-day-light-savings', /// title: 'Daylight Time Saving', /// enabledLabel: 'Enabled', /// disabledLabel: 'Disabled', /// leading: Icon(Icons.timelapse), /// ), /// SwitchSettingsTile( /// settingKey: 'key-dark-mode', /// title: 'Dark Mode', /// enabledLabel: 'Enabled', /// disabledLabel: 'Disabled', /// leading: Icon(Icons.palette), /// ), /// ], /// ); /// ``` class SettingsGroup extends StatelessWidget { /// title string for the tile final String title; /// subtitle string for the tile final String subtitle; /// title text style final TextStyle? titleTextStyle; /// subtitle text style final TextStyle? subtitleTextStyle; /// List of the widgets which are to be shown under the title as a group final List children; final Alignment titleAlignment; const SettingsGroup({ super.key, required this.title, required this.children, this.subtitle = '', this.titleTextStyle, this.subtitleTextStyle, this.titleAlignment = Alignment.centerLeft, }); @override Widget build(BuildContext context) { var elements = [ Container( padding: const EdgeInsets.only(top: 16.0, left: 16.0, right: 22.0), child: Align( alignment: titleAlignment, child: Text( title.toUpperCase(), style: titleTextStyle ?? groupStyle(context), ), ), ), ]; if (subtitle.isNotEmpty) { elements.addAll([ Container( padding: const EdgeInsets.all(16.0), child: Align( alignment: Alignment.centerLeft, child: Text( subtitle, style: subtitleTextStyle, ), ), ), _SettingsTileDivider(), ]); } elements.addAll(children); return Wrap( children: [ Column( children: elements, ) ], ); } TextStyle groupStyle(BuildContext context) { return TextStyle( color: Theme.of(context).colorScheme.secondary, fontSize: 12.0, fontWeight: FontWeight.bold, ); } } /// --------- Common Settings widgets ---------- /// /// A Setting widget which allows user a text input in a [TextFormField] /// /// This widget by default is a [_ModalSettingsTile]. Meaning, this input field /// will be shown in a dialog view. /// /// Example: /// /// ```dart /// TextInputSettingsTile( /// title: 'User Name', /// settingKey: 'key-user-name', /// initialValue: 'admin', /// validator: (String username) { /// if (username != null && username.length > 3) { /// return null; /// } /// return "User Name can't be smaller than 4 letters"; /// }, /// borderColor: Colors.blueAccent, /// errorColor: Colors.deepOrangeAccent, /// ); /// /// ``` /// /// OR /// /// ``` dart /// TextInputSettingsTile( /// title: 'password', /// settingKey: 'key-user-password', /// obscureText: true, /// validator: (String password) { /// if (password != null && password.length > 6) { /// return null; /// } /// return "Password can't be smaller than 7 letters"; /// }, /// borderColor: Colors.blueAccent, /// errorColor: Colors.deepOrangeAccent, /// ); /// ``` class TextInputSettingsTile extends StatefulWidget { /// Settings Key string for storing the text in cache (assumed to be unique) final String settingKey; /// initial value to be filled in the text field, default = '' final String initialValue; /// title for the settings tile final String title; /// title text style final TextStyle? titleTextStyle; /// subtitle text style final TextStyle? subtitleTextStyle; /// flag which represents the state of the settings, if false the the tile will /// ignore all the user inputs, default = true final bool enabled; /// Validation mode helps use customize the way the input text field is /// validated for proper input values. /// /// [AutovalidateMode.disabled] - Never auto validate, equivalent of `autoValidate = false` /// [AutovalidateMode.always] - Always auto validate, equivalent of `autoValidate = true` /// [AutovalidateMode.onUserInteraction] - Only auto validate if user interacts with it final AutovalidateMode autovalidateMode; /// flag which represents if the text field will be focused by default /// or not /// if true, then the text field will be in focus other wise it will not be /// in focus by default, default = true final bool autoFocus; /// flag which represents if the text will be automatically selected on focus final bool selectAllOnFocus; /// on change callback for handling the value change final OnChanged? onChange; /// validator for input validation final FormFieldValidator? validator; /// flag which represents the state of obscureText in the [TextFormField] /// default = false final bool obscureText; /// Color of the border of the [TextFormField] final Color? borderColor; /// Color of the border of the [TextFormField], when there's an error /// or input is not passed through the validation final Color? errorColor; /// [TextInputType] of the [TextFormField] to set the keyboard type to name, phone, etc. final TextInputType? keyboardType; /// form helper text final String? helperText; /// list of inputFormatters final List? inputFormatters; const TextInputSettingsTile({ super.key, required this.title, required this.settingKey, this.initialValue = '', this.enabled = true, this.autovalidateMode = AutovalidateMode.onUserInteraction, this.autoFocus = true, this.selectAllOnFocus = false, this.onChange, this.validator, this.obscureText = false, this.borderColor, this.errorColor, this.keyboardType, this.titleTextStyle, this.subtitleTextStyle, this.helperText, this.inputFormatters, }); @override State createState() => _TextInputSettingsTileState(); } class _TextInputSettingsTileState extends State { final GlobalKey _formKey = GlobalKey(); late TextEditingController _controller; late FocusNode _focusNode; @override void initState() { super.initState(); _controller = TextEditingController(); _focusNode = FocusNode(); _focusNode.addListener(() { if (widget.selectAllOnFocus) { _controller.selection = TextSelection(baseOffset: 0, extentOffset: _controller.text.length); } }); } @override Widget build(BuildContext context) { return ValueChangeObserver( cacheKey: widget.settingKey, defaultValue: widget.initialValue, builder: (BuildContext context, String value, OnChanged onChanged) { WidgetsBinding.instance.addPostFrameCallback((_) { _controller.text = value; }); return SettingsContainer( children: [ _ModalSettingsTile( title: widget.title, subtitle: widget.obscureText ? '' : value, titleTextStyle: widget.titleTextStyle, subtitleTextStyle: widget.subtitleTextStyle, enabled: widget.enabled, showConfirmation: true, onConfirm: () => _submitText(_controller.text), onCancel: () { _controller.text = Settings.getValue(widget.settingKey) ?? ''; }, children: [ _buildTextField(context, value, onChanged), ], ), ], ); }, ); } Widget _buildTextField( BuildContext context, String value, OnChanged onChanged) { final borderColor = widget.borderColor ?? Colors.blue; final errorColor = widget.errorColor ?? Colors.red; return Padding( padding: const EdgeInsets.all(16.0), child: Form( key: _formKey, child: TextFormField( autofocus: widget.autoFocus, controller: _controller, focusNode: _focusNode, autovalidateMode: widget.autovalidateMode, enabled: widget.enabled, validator: widget.enabled ? widget.validator : null, onSaved: widget.enabled ? (value) => _onSave(value, onChanged) : null, obscureText: widget.obscureText, keyboardType: widget.keyboardType, cursorColor: borderColor, inputFormatters: widget.inputFormatters ?? [], decoration: InputDecoration( helperText: widget.helperText, errorMaxLines: 3, helperMaxLines: 3, errorStyle: TextStyle( color: errorColor, ), errorBorder: OutlineInputBorder( borderRadius: const BorderRadius.all( Radius.circular(5.0), ), borderSide: BorderSide(color: errorColor), ), border: OutlineInputBorder( borderRadius: const BorderRadius.all( Radius.circular(5.0), ), borderSide: BorderSide( color: borderColor, ), ), focusedBorder: OutlineInputBorder( borderRadius: const BorderRadius.all( Radius.circular(5.0), ), borderSide: BorderSide( color: borderColor, ), ), ), ), ), ); } bool _submitText(String newValue) { var isValid = true; final state = _formKey.currentState; if (state != null) { isValid = state.validate(); } if (isValid) { state?.save(); return true; } return false; } void _onSave(String? newValue, OnChanged onChanged) { if (newValue == null) return; WidgetsBinding.instance.addPostFrameCallback((_) { onChanged(newValue); widget.onChange?.call(newValue); }); } } /// [SwitchSettingsTile] is a widget that has a [Switch] with given title, /// subtitle and default value/status of the switch /// /// This widget supports an additional list of widgets to display /// when the switch is enabled. These optional list of widgets is accessed /// through `childrenIfEnabled` property of this widget. /// /// This widget works similar to [CheckboxSettingsTile]. /// /// Example: /// /// ```dart /// SwitchSettingsTile( /// leading: Icon(Icons.developer_mode), /// settingKey: 'key-switch-dev-mode', /// title: 'Developer Settings', /// onChange: (value) { /// debugPrint('key-switch-dev-mod: $value'); /// }, /// childrenIfEnabled: [ /// CheckboxSettingsTile( /// leading: Icon(Icons.adb), /// settingKey: 'key-is-developer', /// title: 'Developer Mode', /// onChange: (value) { /// debugPrint('key-is-developer: $value'); /// }, /// ), /// SwitchSettingsTile( /// leading: Icon(Icons.usb), /// settingKey: 'key-is-usb-debugging', /// title: 'USB Debugging', /// onChange: (value) { /// debugPrint('key-is-usb-debugging: $value'); /// }, /// ), /// SimpleSettingsTile( /// title: 'Root Settings', /// subtitle: 'These settings is not accessible', /// enabled: false, /// ) /// ], /// ); /// ``` class SwitchSettingsTile extends StatelessWidget { /// Settings Key string for storing the state of switch in cache (assumed to be unique) final String settingKey; /// initial value to be used as state of the switch, default = false final bool defaultValue; /// title for the settings tile final String title; /// subtitle for the settings tile, default = '' final String subtitle; /// title text style final TextStyle? titleTextStyle; /// subtitle text style final TextStyle? subtitleTextStyle; /// flag which represents the state of the settings, if false the the tile will /// ignore all the user inputs, default = true final bool enabled; /// on change callback for handling the value change final OnChanged? onChange; /// A Widget that will be displayed in the front of the tile final Widget? leading; /// A specific text that will be displayed as subtitle when switch is set to enable state /// if provided, default = '' final String enabledLabel; /// A specific text that will be displayed as subtitle when switch is set to disable state /// if provided, default = '' final String disabledLabel; /// A List of widgets that will be displayed when the switch is set to enable /// state, Any flutter widget can be added in this list final List? childrenIfEnabled; final Color? activeColor; final bool showDivider; const SwitchSettingsTile({ super.key, required this.title, required this.settingKey, this.defaultValue = false, this.enabled = true, this.onChange, this.leading, this.enabledLabel = '', this.disabledLabel = '', this.childrenIfEnabled, this.subtitle = '', this.titleTextStyle, this.subtitleTextStyle, this.activeColor, this.showDivider = true, }); @override Widget build(BuildContext context) { return ValueChangeObserver( cacheKey: settingKey, defaultValue: defaultValue, builder: (BuildContext context, bool value, OnChanged onChanged) { Widget mainWidget = _SettingsTile( leading: leading, title: title, subtitle: getSubtitle(value), onTap: () => _onSwitchChange(context, !value, onChanged), enabled: enabled, titleTextStyle: titleTextStyle, subtitleTextStyle: subtitleTextStyle, showDivider: showDivider, child: _SettingsSwitch( value: value, onChanged: (value) => _onSwitchChange(context, value, onChanged), enabled: enabled, activeColor: activeColor, ), ); var finalWidget = getFinalWidget( context, mainWidget, value, childrenIfEnabled, ); return finalWidget; }, ); } Future _onSwitchChange( BuildContext context, bool? value, OnChanged onChanged) async { if (value == null) return; onChanged(value); onChange?.call(value); } String getSubtitle(bool currentStatus) { if (subtitle.isNotEmpty) { return subtitle; } var label = ''; if (currentStatus && enabledLabel.isNotEmpty) { label = enabledLabel; } if (!currentStatus && disabledLabel.isNotEmpty) { label = disabledLabel; } return label; } Widget getFinalWidget(BuildContext context, Widget mainWidget, bool currentValue, List? childrenIfEnabled) { if (childrenIfEnabled == null || !currentValue) { return SettingsContainer( children: [mainWidget], ); } final children = getPaddedParentChildrenList(childrenIfEnabled); children.insert(0, mainWidget); return SettingsContainer( children: children, ); } } /// [CheckboxSettingsTile] is a widget that has a [Checkbox] with given title, /// subtitle and default value/status of the Checkbox /// /// This widget supports an additional list of widgets to display /// when the Checkbox is checked. These optional list of widgets is accessed /// through `childrenIfEnabled` property of this widget. /// /// This widget works similar to [SwitchSettingsTile]. /// /// Example: /// /// ```dart /// CheckboxSettingsTile( /// leading: Icon(Icons.developer_mode), /// settingKey: 'key-check-box-dev-mode', /// title: 'Developer Settings', /// onChange: (value) { /// debugPrint('key-check-box-dev-mode: $value'); /// }, /// childrenIfEnabled: [ /// CheckboxSettingsTile( /// leading: Icon(Icons.adb), /// settingKey: 'key-is-developer', /// title: 'Developer Mode', /// onChange: (value) { /// debugPrint('key-is-developer: $value'); /// }, /// ), /// SwitchSettingsTile( /// leading: Icon(Icons.usb), /// settingKey: 'key-is-usb-debugging', /// title: 'USB Debugging', /// onChange: (value) { /// debugPrint('key-is-usb-debugging: $value'); /// }, /// ), /// ], /// ); /// ``` class CheckboxSettingsTile extends StatelessWidget { /// Settings Key string for storing the state of checkbox in cache (assumed to be unique) final String settingKey; /// initial value to be used as state of the checkbox, default = false final bool defaultValue; /// title for the settings tile final String title; /// subtitle for the settings tile, default = '' final String subtitle; /// title text style final TextStyle? titleTextStyle; /// subtitle text style final TextStyle? subtitleTextStyle; /// flag which represents the state of the settings, if false the the tile will /// ignore all the user inputs, default = true final bool enabled; /// on change callback for handling the value change final OnChanged? onChange; /// A Widget that will be displayed in the front of the tile final Widget? leading; /// A specific text that will be displayed as subtitle when checkbox is set to enable state /// if provided, default = '' final String enabledLabel; /// A specific text that will be displayed as subtitle when checkbox is set to disable state /// if provided, default = '' final String disabledLabel; /// A List of widgets that will be displayed when the checkbox is set to enable /// state, Any flutter widget can be added in this list final List? childrenIfEnabled; final bool showDivider; const CheckboxSettingsTile({ super.key, required this.title, required this.settingKey, this.defaultValue = false, this.enabled = true, this.onChange, this.leading, this.enabledLabel = '', this.disabledLabel = '', this.childrenIfEnabled, this.subtitle = '', this.titleTextStyle, this.subtitleTextStyle, this.showDivider = true, }); @override Widget build(BuildContext context) { return ValueChangeObserver( cacheKey: settingKey, defaultValue: defaultValue, builder: (BuildContext context, bool value, OnChanged onChanged) { var mainWidget = _SettingsTile( leading: leading, title: title, enabled: enabled, subtitle: getSubtitle(value), onTap: () => _onCheckboxChange(!value, onChanged), titleTextStyle: titleTextStyle, subtitleTextStyle: subtitleTextStyle, showDivider: showDivider, child: _SettingsCheckbox( value: value, onChanged: (value) => _onCheckboxChange(value, onChanged), enabled: enabled, ), ); var finalWidget = getFinalWidget( context, mainWidget, value, childrenIfEnabled, ); return finalWidget; }, ); } Future _onCheckboxChange(bool? value, OnChanged onChanged) async { if (value == null) return; onChanged(value); onChange?.call(value); } String getSubtitle(bool currentStatus) { if (subtitle.isNotEmpty) { return subtitle; } var label = ''; if (currentStatus && enabledLabel.isNotEmpty) { label = enabledLabel; } if (!currentStatus && disabledLabel.isNotEmpty) { label = disabledLabel; } return label; } Widget getFinalWidget(BuildContext context, Widget mainWidget, bool currentValue, List? childrenIfEnabled) { if (childrenIfEnabled == null || !currentValue) { return SettingsContainer( children: [mainWidget], ); } final children = getPaddedParentChildrenList(childrenIfEnabled); children.insert(0, mainWidget); return SettingsContainer( children: children, ); } } /// [RadioSettingsTile] is a widget that has a list of [Radio] widgets with given title, /// subtitle and default/group value which determines which Radio will be selected /// initially. /// /// This widget support Any type of values which should be put in the preference. /// However, since any types of the value is supported, the input for this widget /// is a Map to the required values with their string representation. /// /// For example if the required value type is a boolean then the values map can /// be as following: /// { /// true: 'Enabled', /// false: 'Disabled' /// } /// /// So, if the `Enabled` value radio is selected then the value `true` will be /// stored in the preference /// /// Complete Example: /// /// ```dart /// RadioSettingsTile( /// title: 'Preferred Sync Period', /// settingKey: 'key-radio-sync-period', /// values: { /// 0: 'Never', /// 1: 'Daily', /// 7: 'Weekly', /// 15: 'Fortnight', /// 30: 'Monthly', /// }, /// selected: 0, /// onChange: (value) { /// debugPrint('key-radio-sync-period: $value days'); /// }, /// ) /// ``` class RadioSettingsTile extends StatefulWidget { /// Settings Key string for storing the state of Radio buttons in cache (assumed to be unique) final String settingKey; /// Selected value in the radio button group otherwise known as group value final T selected; /// A map containing unique values along with the display name final Map values; /// title for the settings tile final String title; /// subtitle for the settings tile, default = '' final String subtitle; /// title text style final TextStyle? titleTextStyle; /// subtitle text style final TextStyle? subtitleTextStyle; /// flag which represents the state of the settings, if false the the tile will /// ignore all the user inputs, default = true final bool enabled; /// flag which allows showing the display names along with the radio button final bool showTitles; /// on change callback for handling the value change final OnChanged? onChange; /// A Widget that will be displayed in the front of the tile final Widget? leading; final Color? activeColor; final bool showDivider; const RadioSettingsTile({ super.key, required this.title, required this.settingKey, required this.selected, required this.values, this.enabled = true, this.onChange, this.leading, this.showTitles = true, this.subtitle = '', this.titleTextStyle, this.subtitleTextStyle, this.activeColor, this.showDivider = true, }); @override State> createState() => _RadioSettingsTileState(); } class _RadioSettingsTileState extends State> { late T selectedValue; @override void initState() { selectedValue = widget.selected; super.initState(); } Future _onRadioChange(T? value, OnChanged onChanged) async { if (value == null) return; selectedValue = value; onChanged(value); widget.onChange?.call(value); } @override Widget build(BuildContext context) { return ValueChangeObserver( cacheKey: widget.settingKey, defaultValue: selectedValue, builder: (BuildContext context, T value, OnChanged onChanged) { return SettingsContainer( children: [ Visibility( visible: showTitles, child: _SimpleHeaderTile( title: widget.title, subtitle: widget.subtitle.isNotEmpty ? widget.subtitle : widget.values[selectedValue], leading: widget.leading, titleTextStyle: widget.titleTextStyle, subtitleTextStyle: widget.subtitleTextStyle, ), ), _buildRadioTiles(context, value, onChanged, widget.activeColor) ], ); }, ); } bool get showTitles => widget.showTitles; Widget _buildRadioTiles(BuildContext context, T groupValue, OnChanged onChanged, Color? activeColor) { var radioList = widget.values.entries.map((MapEntry entry) { return _SettingsTile( title: entry.value, titleTextStyle: widget.titleTextStyle, onTap: () => _onRadioChange(entry.key, onChanged), enabled: widget.enabled, showDivider: widget.showDivider, child: _SettingsRadio( value: entry.key, onChanged: (newValue) => _onRadioChange(newValue, onChanged), enabled: widget.enabled, groupValue: groupValue, activeColor: activeColor, ), ); }).toList(); return Column( children: radioList, ); } } /// [DropDownSettingsTile] is a widget that has a list of [DropdownMenuItem]s /// with given title, subtitle and default/group value which determines /// which value will be set to selected initially. /// /// This widget support Any type of values which should be put in the preference. /// /// However, since any types of the value is supported, the input for this widget /// is a Map to the required values with their string representation. /// /// For example if the required value type is a boolean then the values map can /// be as following: /// { /// true: 'Enabled', /// false: 'Disabled' /// } /// /// So, if the `Enabled` value is selected then the value `true` will be /// stored in the preference /// /// Complete Example: /// /// ```dart /// DropDownSettingsTile( /// title: 'E-Mail View', /// settingKey: 'key-dropdown-email-view', /// values: { /// 2: 'Simple', /// 3: 'Adjusted', /// 4: 'Normal', /// 5: 'Compact', /// 6: 'Squeezed', /// }, /// selected: 2, /// onChange: (value) { /// debugPrint('key-dropdown-email-view: $value'); /// }, /// ); /// ``` class DropDownSettingsTile extends StatefulWidget { /// Settings Key string for storing the state of Radio buttons in cache (assumed to be unique) final String settingKey; /// Selected value in the radio button group otherwise known as group value final T selected; /// A map containing unique values along with the display name final Map values; /// title for the settings tile final String title; /// subtitle for the settings tile, default = '' final String subtitle; /// title text style final TextStyle? titleTextStyle; /// subtitle text style final TextStyle? subtitleTextStyle; /// The widget shown in front of the title final Widget? leading; /// Alignment of the dropdown. Defaults to [AlignmentDirectional.centerEnd]. final AlignmentGeometry alignment; /// flag which represents the state of the settings, if false the the tile will /// ignore all the user inputs, default = true final bool enabled; /// on change callback for handling the value change final OnChanged? onChange; final bool showDivider; const DropDownSettingsTile({ super.key, required this.title, required this.settingKey, required this.selected, required this.values, this.enabled = true, this.onChange, this.subtitle = '', this.leading, this.alignment = AlignmentDirectional.centerEnd, this.titleTextStyle, this.subtitleTextStyle, this.showDivider = true, }); @override State> createState() => _DropDownSettingsTileState(); } class _DropDownSettingsTileState extends State> { late T selectedValue; @override void initState() { selectedValue = widget.selected; super.initState(); } @override Widget build(BuildContext context) { return ValueChangeObserver( cacheKey: widget.settingKey, defaultValue: selectedValue, builder: (BuildContext context, T value, OnChanged onChanged) { return SettingsContainer( children: [ _SettingsTile( title: widget.title, subtitle: widget.subtitle, leading: widget.leading, enabled: widget.enabled, titleTextStyle: widget.titleTextStyle, subtitleTextStyle: widget.subtitleTextStyle, showDivider: widget.showDivider, child: _SettingsDropDown( selected: value, alignment: widget.alignment, values: widget.values.keys.toList().cast(), onChanged: (newValue) => _handleDropDownChange(newValue, onChanged), enabled: widget.enabled, itemBuilder: (T value) { return Text(widget.values[value]!); }, ), ) ], ); }, ); } Future _handleDropDownChange(T? value, OnChanged onChanged) async { if (value == null) return; selectedValue = value; onChanged(value); widget.onChange?.call(value); } } /// [SliderSettingsTile] is a widget that has a slider given title, /// subtitle and default value which determines what the slider's position /// will be set initially. /// /// Example: /// /// ```dart /// SliderSettingsTile( /// title: 'Volume', /// settingKey: 'key-slider-volume', /// defaultValue: 20, /// min: 0, /// max: 100, /// step: 1, /// leading: Icon(Icons.volume_up), /// onChange: (value) { /// debugPrint('key-slider-volume: $value'); /// }, /// ); /// ``` class SliderSettingsTile extends StatefulWidget { /// Settings Key string for storing the state of Slider in cache (assumed to be unique) final String settingKey; /// Selected value in the radio button group otherwise known as group value /// default = 0.0 final double defaultValue; /// title for the settings tile final String title; /// subtitle for the settings tile, default = '' final String subtitle; /// title text style final TextStyle? titleTextStyle; /// subtitle text style final TextStyle? subtitleTextStyle; /// flag which represents the state of the settings, if false the the tile will /// ignore all the user inputs, default = true final bool enabled; /// flag which allows updating the value of setting immediately when the /// slider is moved, default = true /// /// If this flag is enabled then [onChangeStart] & [onChangeEnd] callbacks are /// ignored & will not be executed /// final bool eagerUpdate; /// minimum allowed value for the slider, in other terms a start value /// for the slider final double min; /// maximum allowed value for the slider, in other terms a end value /// for the slider final double max; /// a step value which will be used to move the slider. /// default = 1.0 /// /// i.e. if the step = 1.0 then moving slider from left to right /// will result in following values in order: /// 1.0, 2.0, 3.0, 4.0, ..., 100.0 /// /// if the step = 5.0 then the same slider movement will result in: /// 5.0, 10.0, 15.0, 20.0, ..., 100.0 final double step; /// on change callback for handling the value change final OnChanged? onChange; /// callback for fetching the value slider movement starts final OnChanged? onChangeStart; /// callback for fetching the value slider movement ends final OnChanged? onChangeEnd; /// callback for fetching the value slider movement starts final Widget? leading; /// Value to be used as precision when printing the current value in widget /// /// Basically this value is used for input in [double.toStringAsFixed] method /// while printing display value /// /// default = 2, you'll need to adjust the precision according to step value /// /// Note: /// However this does not affect the actual value in the onChange callbacks /// /// For example: /// case 1: /// current value: 5.2500001 /// decimalPrecision: 0 /// result: /// callback value: 5.2500001 /// display value: 5 /// /// case 2: /// current value: 5.2500001 /// decimalPrecision: 1 /// result: /// callback value: 5.2500001 /// display value: 5.2 /// /// case 3: /// current value: 5.2500001 /// decimalPrecision: 2 /// result: /// callback value: 5.2500001 /// display value: 5.25 final int decimalPrecision; final bool showDivider; final Color? activeColor; final Color? inActiveColor; const SliderSettingsTile({ super.key, required this.title, required this.settingKey, required this.min, required this.max, this.subtitle = '', this.defaultValue = 0.0, this.enabled = true, this.eagerUpdate = true, this.step = 1.0, this.onChange, this.onChangeStart, this.onChangeEnd, this.leading, this.decimalPrecision = 2, this.titleTextStyle, this.subtitleTextStyle, this.activeColor, this.inActiveColor, this.showDivider = true, }); @override State createState() => _SliderSettingsTileState(); } class _SliderSettingsTileState extends State { late double currentValue; @override void initState() { currentValue = widget.defaultValue; super.initState(); } @override Widget build(BuildContext context) { return ValueChangeObserver( cacheKey: widget.settingKey, defaultValue: currentValue, builder: (BuildContext context, double value, OnChanged onChanged) { // debugPrint('creating settings Tile: ${widget.settingKey}'); return SettingsContainer( children: [ _SimpleHeaderTile( title: widget.title, subtitle: widget.subtitle.isNotEmpty ? widget.subtitle : value.toStringAsFixed(widget.decimalPrecision), leading: widget.leading, titleTextStyle: widget.titleTextStyle, subtitleTextStyle: widget.subtitleTextStyle, ), _SettingsSlider( onChanged: (newValue) => _handleSliderChanged(newValue, onChanged), onChangeStart: (newValue) => _handleSliderChangeStart(newValue, onChanged), onChangeEnd: (newValue) => _handleSliderChangeEnd(newValue, onChanged), enabled: widget.enabled, eagerUpdate: widget.eagerUpdate, value: value, max: widget.max, min: widget.min, step: widget.step, activeColor: widget.activeColor, inActiveColor: widget.inActiveColor, ), if (widget.showDivider) _SettingsTileDivider(), ], ); }, ); } void _updateWidget(double newValue, OnChanged onChanged) { currentValue = newValue; onChanged(newValue); } void _handleSliderChanged(double newValue, OnChanged onChanged) { _updateWidget(newValue, onChanged); widget.onChange?.call(newValue); } void _handleSliderChangeStart(double newValue, OnChanged onChanged) { _updateWidget(newValue, onChanged); widget.onChangeStart?.call(newValue); } Future _handleSliderChangeEnd( double newValue, OnChanged onChanged) async { _updateWidget(newValue, onChanged); widget.onChangeEnd?.call(newValue); } } /// [ColorPickerSettingsTile] is a widget which allows user to /// select the a color from a set of Material color choices. /// /// Since, [Color] is an in-memory object type, the serialized version /// of the value of this widget will be a Hex value [String] of the selected /// color. /// /// This conversion string <-> color, makes this easy to check/debug the values /// from the storage/preference manually. /// /// The color panel shown in the widget is provided by `flutter_material_color_picker` library. /// /// Example: /// /// ```dart /// ColorPickerSettingsTile( /// settingKey: 'key-color-picker', /// title: 'Accent Color', /// defaultValue: Colors.blue, /// onChange: (value) { /// debugPrint('key-color-picker: $value'); /// }, /// ); /// ``` class ColorPickerSettingsTile extends StatefulWidget { /// Settings Key string for storing the state of Color picker in cache (assumed to be unique) final String settingKey; /// Default value of the color as a Hex String, default = '#ff000000' final String defaultStringValue; /// Default value of the color final Color? defaultValue; /// title for the settings tile final String title; /// subtitle for the settings tile, default = '' final String subtitle; /// title text style final TextStyle? titleTextStyle; /// subtitle text style final TextStyle? subtitleTextStyle; /// The widget shown in front of the title final Widget? leading; /// flag which represents the state of the settings, if false the the tile will /// ignore all the user inputs, default = true final bool enabled; /// on change callback for handling the value change final OnChanged? onChange; final bool showDivider; const ColorPickerSettingsTile({ super.key, required this.title, required this.settingKey, this.defaultValue, this.enabled = true, this.onChange, this.defaultStringValue = '#ff000000', this.subtitle = '', this.leading, this.titleTextStyle, this.subtitleTextStyle, this.showDivider = true, }); @override State createState() => _ColorPickerSettingsTileState(); } class _ColorPickerSettingsTileState extends State { late String currentValue; @override void initState() { super.initState(); if (widget.defaultValue != null) { currentValue = ConversionUtils.stringFromColor(widget.defaultValue!); } else { currentValue = widget.defaultStringValue; } } @override Widget build(BuildContext context) { return ValueChangeObserver( cacheKey: widget.settingKey, defaultValue: currentValue, builder: (BuildContext context, String value, OnChanged onChanged) { // debugPrint('creating settings Tile: ${widget.settingKey}'); return _SettingsColorPicker( title: widget.title, value: value, leading: widget.leading, enabled: widget.enabled, subtitle: widget.subtitle, onChanged: (color) => _handleColorChanged(color, onChanged), titleTextStyle: widget.titleTextStyle, subtitleTextStyle: widget.subtitleTextStyle, showDivider: widget.showDivider, ); }, ); } Future _handleColorChanged( String color, OnChanged onChanged) async { currentValue = color; onChanged(color); final colorFromString = ConversionUtils.colorFromString(color); widget.onChange?.call(colorFromString); } } /// [RadioModalSettingsTile] widget is the dialog version of the /// [RadioSettingsTile] widget. /// /// Meaning this widget is similar to a [RadioSettingsTile] shown in a dialog. /// /// Use of this widget is similar to the [RadioSettingsTile], only the displayed /// widget will be in a different position. i.e instead of the settings screen, /// it will be shown in a dialog above the settings screen. /// /// Example: /// ```dart /// RadioModalSettingsTile( /// title: 'Preferred Sync Period', /// settingKey: 'key-radio-sync-period', /// values: { /// 0: 'Never', /// 1: 'Daily', /// 7: 'Weekly', /// 15: 'Fortnight', /// 30: 'Monthly', /// }, /// selected: 0, /// onChange: (value) { /// debugPrint('key-radio-sync-period: $value days'); /// }, /// ); /// ``` /// class RadioModalSettingsTile extends StatefulWidget { /// Settings Key string for storing the state of Radio setting in cache (assumed to be unique) final String settingKey; /// Selected value or group value of the radio buttons final T selected; /// A map containing unique values along with the display name final Map values; /// title for the settings tile final String title; /// subtitle for the settings tile, default = '' final String subtitle; /// title text style final TextStyle? titleTextStyle; /// subtitle text style final TextStyle? subtitleTextStyle; /// The widget shown in front of the title final Widget? leading; /// flag which represents the state of the settings, if false the the tile will /// ignore all the user inputs, default = true final bool enabled; /// flag which allows showing the display names along with the radio button final bool showTitles; /// on change callback for handling the value change final OnChanged? onChange; final Color? activeColor; final bool showDivider; const RadioModalSettingsTile({ super.key, required this.title, required this.settingKey, required this.selected, required this.values, this.enabled = true, this.showTitles = false, this.onChange, this.subtitle = '', this.leading, this.titleTextStyle, this.subtitleTextStyle, this.activeColor, this.showDivider = true, }); @override State> createState() => _RadioModalSettingsTileState(); } class _RadioModalSettingsTileState extends State> { late T selectedValue; @override void initState() { selectedValue = widget.selected; super.initState(); } Future _onRadioChange(T? value, OnChanged onChanged) async { if (value == null) return; selectedValue = value; onChanged(value); widget.onChange?.call(value); } @override Widget build(BuildContext context) { return ValueChangeObserver( cacheKey: widget.settingKey, defaultValue: selectedValue, builder: (BuildContext context, T value, OnChanged onChanged) { return SettingsContainer( children: [ _ModalSettingsTile( title: widget.title, subtitle: widget.subtitle.isNotEmpty ? widget.subtitle : widget.values[value], leading: widget.leading, titleTextStyle: widget.titleTextStyle, subtitleTextStyle: widget.subtitleTextStyle, children: [ RadioSettingsTile( title: '', showTitles: widget.showTitles, enabled: widget.enabled, values: widget.values, settingKey: widget.settingKey, onChange: (value) => _onRadioChange(value, onChanged), selected: value, activeColor: widget.activeColor, showDivider: widget.showDivider, ), ], ), ], ); }, ); } } /// [SliderModalSettingsTile] widget is the dialog version of the /// [SliderSettingsTile] widget. /// /// Meaning this widget is similar to a [SliderSettingsTile] shown in a dialog. /// /// Use of this widget is similar to the [SliderSettingsTile], only the displayed /// widget will be in a different position. i.e instead of the settings screen, /// it will be shown in a dialog above the settings screen. /// /// Example: /// ```dart /// SliderSettingsTile( /// title: 'Volume', /// settingKey: 'key-slider-volume', /// defaultValue: 20, /// min: 0, /// max: 100, /// step: 1, /// leading: Icon(Icons.volume_up), /// onChange: (value) { /// debugPrint('key-slider-volume: $value'); /// }, /// ); /// ``` /// class SliderModalSettingsTile extends StatefulWidget { /// Settings Key string for storing the state of Slider in cache (assumed to be unique) final String settingKey; /// Selected value in the radio button group otherwise known as group value /// default = 0.0 final double defaultValue; /// title for the settings tile final String title; /// subtitle for the settings tile, default = '' final String subtitle; /// title text style final TextStyle? titleTextStyle; /// subtitle text style final TextStyle? subtitleTextStyle; /// The widget shown in front of the title final Widget? leading; /// flag which represents the state of the settings, if false the the tile will /// ignore all the user inputs, default = true final bool enabled; /// flag which allows updating the value of setting immediately when the /// slider is moved, default = true /// /// If this flag is enabled then [onChangeStart] & [onChangeEnd] callbacks are /// ignored & will not be executed /// final bool eagerUpdate; /// minimum allowed value for the slider, in other terms a start value /// for the slider final double min; /// maximum allowed value for the slider, in other terms a end value /// for the slider final double max; /// a step value which will be used to move the slider. /// default = 1.0 /// /// i.e. if the step = 1.0 then moving slider from left to right /// will result in following values in order: /// 1.0, 2.0, 3.0, 4.0, ..., 100.0 /// /// if the step = 5.0 then the same slider movement will result in: /// 5.0, 10.0, 15.0, 20.0, ..., 100.0 final double step; /// on change callback for handling the value change final OnChanged? onChange; /// callback for fetching the value slider movement starts final OnChanged? onChangeStart; /// callback for fetching the value slider movement ends final OnChanged? onChangeEnd; final Color? activeColor; final Color? inActiveColor; const SliderModalSettingsTile({ super.key, required this.title, required this.settingKey, required this.min, required this.max, this.defaultValue = 0.0, this.enabled = true, this.step = 0.0, this.onChange, this.onChangeStart, this.onChangeEnd, this.subtitle = '', this.leading, this.eagerUpdate = true, this.titleTextStyle, this.subtitleTextStyle, this.activeColor, this.inActiveColor, }); @override State createState() => _SliderModalSettingsTileState(); } class _SliderModalSettingsTileState extends State { late double currentValue; @override void initState() { currentValue = widget.defaultValue; super.initState(); } @override Widget build(BuildContext context) { return ValueChangeObserver( cacheKey: widget.settingKey, defaultValue: currentValue, builder: (BuildContext context, double value, OnChanged onChanged) { // debugPrint('creating settings Tile: ${widget.settingKey}'); return SettingsContainer( children: [ _ModalSettingsTile( title: widget.title, subtitle: widget.subtitle.isNotEmpty ? widget.subtitle : value.toString(), leading: widget.leading, titleTextStyle: widget.titleTextStyle, subtitleTextStyle: widget.subtitleTextStyle, children: [ _SettingsSlider( onChanged: (double newValue) => _handleSliderChanged(newValue, onChanged), onChangeStart: (double newValue) => _handleSliderChangeStart(newValue, onChanged), onChangeEnd: (double newValue) => _handleSliderChangeEnd(newValue, onChanged), enabled: widget.enabled, eagerUpdate: widget.eagerUpdate, value: value, max: widget.max, min: widget.min, step: widget.step, activeColor: widget.activeColor, inActiveColor: widget.inActiveColor, ) ], ), ], ); }, ); } void _handleSliderChanged(double newValue, OnChanged onChanged) { _updateWidget(newValue, onChanged); widget.onChange?.call(newValue); } void _handleSliderChangeStart(double newValue, OnChanged onChanged) { _updateWidget(newValue, onChanged); widget.onChangeStart?.call(newValue); } void _handleSliderChangeEnd(double newValue, OnChanged onChanged) { _updateWidget(newValue, onChanged); widget.onChangeEnd?.call(newValue); } void _updateWidget(double newValue, OnChanged onChanged) { currentValue = newValue; onChanged(newValue); } } /// [SimpleRadioSettingsTile] is a simpler version of /// the [RadioSettingsTile]. /// /// Instead of a Value-String map, this widget just takes a list /// of String values. /// /// Example: /// ```dart /// SimpleRadioSettingsTile( /// title: 'Sync Settings', /// settingKey: 'key-radio-sync-settings', /// values: [ /// 'Never', /// 'Daily', /// 'Weekly', /// 'Fortnight', /// 'Monthly', /// ], /// selected: 'Daily', /// onChange: (value) { /// debugPrint('key-radio-sync-settings: $value'); /// }, /// ); /// ``` class SimpleRadioSettingsTile extends StatelessWidget { /// Settings Key string for storing the state of Radio setting in cache (assumed to be unique) final String settingKey; /// Selected value or group value of the radio buttons final String selected; /// A map containing unique values along with the display name final List values; /// title for the settings tile final String title; /// subtitle for the settings tile, default = '' final String subtitle; /// title text style final TextStyle? titleTextStyle; /// subtitle text style final TextStyle? subtitleTextStyle; /// The widget shown in front of the title final Widget? leading; /// flag which represents the state of the settings, if false the the tile will /// ignore all the user inputs, default = true final bool enabled; /// on change callback for handling the value change final OnChanged? onChange; const SimpleRadioSettingsTile({ super.key, required this.title, required this.settingKey, required this.selected, required this.values, this.enabled = true, this.onChange, this.subtitle = '', this.leading, this.titleTextStyle, this.subtitleTextStyle, }); @override Widget build(BuildContext context) { return RadioSettingsTile( title: title, subtitle: subtitle, leading: leading, settingKey: settingKey, selected: selected, enabled: enabled, onChange: onChange, values: getValues(values), titleTextStyle: titleTextStyle, subtitleTextStyle: subtitleTextStyle, ); } Map getValues(List values) { final valueMap = {}; for (var value in values) { valueMap[value] = value; } return valueMap; } } /// [SimpleDropDownSettingsTile] is a simpler version of /// the [DropDownSettingsTile]. /// /// Instead of a Value-String map, this widget just takes a list /// of String values. /// /// Example: /// ```dart /// SimpleDropDownSettingsTile( /// title: 'Beauty Filter', /// settingKey: 'key-dropdown-beauty-filter', /// values: [ /// 'Simple', /// 'Normal', /// 'Little Special', /// 'Special', /// 'Extra Special', /// 'Bizzar', /// 'Horrific', /// ], /// selected: 'Special', /// onChange: (value) { /// debugPrint('key-dropdown-beauty-filter: $value'); /// }, /// ); /// ``` class SimpleDropDownSettingsTile extends StatelessWidget { /// Settings Key string for storing the state of Radio buttons in cache (assumed to be unique) final String settingKey; /// Selected value in the radio button group otherwise known as group value final String selected; /// A map containing unique values along with the display name final List values; /// title for the settings tile final String title; /// subtitle for the settings tile, default = '' final String subtitle; /// title text style final TextStyle? titleTextStyle; /// subtitle text style final TextStyle? subtitleTextStyle; /// The widget shown in front of the title final Widget? leading; /// flag which represents the state of the settings, if false the the tile will /// ignore all the user inputs, default = true final bool enabled; /// on change callback for handling the value change final OnChanged? onChange; const SimpleDropDownSettingsTile({ super.key, required this.title, required this.settingKey, required this.selected, required this.values, this.enabled = true, this.onChange, this.subtitle = '', this.leading, this.titleTextStyle, this.subtitleTextStyle, }); @override Widget build(BuildContext context) { return DropDownSettingsTile( title: title, subtitle: subtitle, leading: leading, settingKey: settingKey, selected: selected, enabled: enabled, onChange: onChange, values: getValues(values), titleTextStyle: titleTextStyle, subtitleTextStyle: subtitleTextStyle, ); } Map getValues(List values) { final valueMap = {}; for (var value in values) { valueMap[value] = value; } return valueMap; } } ================================================ FILE: pubspec.yaml ================================================ name: flutter_settings_screens description: A Simple plugin to implement settings UI screens for your flutter apps. Customize what you want, From UI elements, behaviour or underlying storage library. repository: https://github.com/GAM3RG33K/flutter_settings_screens homepage: https://github.com/GAM3RG33K/flutter_settings_screens version: 0.3.4 environment: sdk: '>=3.2.4 <4.0.0' dependencies: flutter: sdk: flutter # library to access storage directories path_provider: ^2.0.10 # library to use Provider widgets provider: ^6.0.3 # library to store the preference/settings using shared_preferences library shared_preferences: ^2.0.15 dev_dependencies: flutter_test: sdk: flutter # to enforce dart coding standard flutter_lints: ^3.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter. flutter: ================================================ FILE: test/flutter_settings_screens_test.dart ================================================ //import 'package:flutter/services.dart'; //import 'package:flutter_test/flutter_test.dart'; //import 'package:flutter_settings_screens/flutter_settings_screens.dart'; // //void main() { // const MethodChannel channel = MethodChannel('flutter_settings_screens'); // // TestWidgetsFlutterBinding.ensureInitialized(); // // setUp(() { // channel.setMockMethodCallHandler((MethodCall methodCall) async { // return '42'; // }); // }); // // tearDown(() { // channel.setMockMethodCallHandler(null); // }); // // test('getPlatformVersion', () async { // expect(await FlutterSettingsScreens.platformVersion, '42'); // }); //}