├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── example ├── .gitignore ├── .metadata ├── analysis_options.yaml ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── 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 │ ├── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ ├── Contents.json │ │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ │ ├── Icon-App-20x20@1x.png │ │ │ │ ├── Icon-App-20x20@2x.png │ │ │ │ ├── Icon-App-20x20@3x.png │ │ │ │ ├── Icon-App-29x29@1x.png │ │ │ │ ├── Icon-App-29x29@2x.png │ │ │ │ ├── Icon-App-29x29@3x.png │ │ │ │ ├── Icon-App-40x40@1x.png │ │ │ │ ├── Icon-App-40x40@2x.png │ │ │ │ ├── Icon-App-40x40@3x.png │ │ │ │ ├── Icon-App-60x60@2x.png │ │ │ │ ├── Icon-App-60x60@3x.png │ │ │ │ ├── Icon-App-76x76@1x.png │ │ │ │ ├── Icon-App-76x76@2x.png │ │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ │ └── LaunchImage.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── LaunchImage.png │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ └── README.md │ │ ├── Base.lproj │ │ │ ├── LaunchScreen.storyboard │ │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h │ └── RunnerTests │ │ └── RunnerTests.swift ├── lib │ ├── data_models.dart │ ├── data_models.mapper.dart │ ├── main.dart │ ├── pages │ │ ├── collection_column_page.dart │ │ ├── int_text_form_field_page.dart │ │ └── string_text_form_field_page.dart │ ├── utils │ │ ├── extensions.dart │ │ └── page_mixin.dart │ ├── views │ │ └── example_scaffold.dart │ └── widgets │ │ └── dismiss_keyboard_icon_button.dart ├── pubspec.lock └── pubspec.yaml ├── lib ├── data_widgets.dart └── src │ ├── collection_flex.dart │ ├── enum_stack.dart │ └── typed_text_form_field.dart ├── pubspec.yaml └── test └── src └── typed_text_form_field_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. 26 | /pubspec.lock 27 | **/doc/api/ 28 | .dart_tool/ 29 | .flutter-plugins 30 | .flutter-plugins-dependencies 31 | build/ 32 | coverage/lcov.info -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "c23637390482d4cf9598c3ce3f2be31aa7332daf" 8 | channel: "stable" 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.1 2 | 3 | * TODO: Describe initial release. 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # data_widgets 2 | 3 | ## problem 4 | 5 | currently a lot of time is spent in flutter syncing widgets with data, usually by way of controllers provided by the framework (TextEditingController, PageController, ScrollController, etc). Also, we often need to manipulate numbers or other primitive types in a TextFormField, but there is a lot of configuration involved. 6 | 7 | ## goal 8 | 9 | to provide a set of simple widgets whose sole purpose is to manipulate or respond to data 10 | 11 | ## vision 12 | 13 | ### ListView 14 | 15 | Imagine using `ListView` to display a list of values. The two common ways are read-only and don't integrate with the `List` type. 16 | 17 | ```dart 18 | final values = [1,2,3,4]; 19 | 20 | ListView( 21 | children: [ 22 | for(final x in values) 23 | ListTile(title: Text("$x")) 24 | ] 25 | ) 26 | ``` 27 | 28 | consider a version of `ListView` that instead accepts a source `List`, but also has an `onChange` callback and allows the user to drag-to-reorder or delete items, while also animating items being added/moved/deleted when the source `List` is mutated by app code. 29 | 30 | 31 | ```dart 32 | final values = [1,2,3,4]; 33 | 34 | CollectionListView( 35 | source: values, 36 | actions: [CollectionAction.move, CollectionAction.delete], 37 | onChange: (updatedValues){ 38 | setState(()=>values = updatedValues); 39 | }, 40 | builder: (context, x){ 41 | return ListTile(title: Text("$x")) 42 | }, 43 | ) 44 | ``` 45 | 46 | ## areas to disrupt 47 | 48 | - TextFormField -> IntTextFormField 49 | - TextFormField -> DoubleTextFormField 50 | - TextFormField -> EmailTextFormField 51 | - PageView + PageController + Enum -> EnumPageView 52 | - Stack + Custom Animation + Enum -> EnumStack 53 | - ReorderableListView + Callback + Slideable -> CollectionColumn / CollectionListView 54 | 55 | ## how i solve this problem 56 | 57 | 1. think of a data type, ie `List` 58 | 1. then think of how people manipulate it in ui, ie drag to reorder, swipe to delete 59 | 1. look at flutter built in widgets to see if they can get close with a slight api change 60 | 1. look at swiftui to see if they have solved this problem already (they almost certainly have) 61 | 1. then make a widget that allows the user to directly interact with a data type in code, typically with a simple `source:` property and `onChange()` callback 62 | 63 | ## contributing 64 | 65 | - all contributions to codebase or example are welcome 66 | - search for `// TODO:` in the codebase 67 | - look at SwiftUI for inspiration. they are the current leader in data driven components 68 | - contact me at [@luke_pighetti](https://x.com/luke_pighetti) on x for questions 69 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | # Additional information about this file can be found at 4 | # https://dart.dev/guides/language/analysis-options 5 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .build/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | .swiftpm/ 13 | migrate_working_dir/ 14 | 15 | # IntelliJ related 16 | *.iml 17 | *.ipr 18 | *.iws 19 | .idea/ 20 | 21 | # The .vscode folder contains launch configuration and tasks you configure in 22 | # VS Code which you may wish to be included in version control, so this line 23 | # is commented out by default. 24 | #.vscode/ 25 | 26 | # Flutter/Dart/Pub related 27 | **/doc/api/ 28 | **/ios/Flutter/.last_build_id 29 | .dart_tool/ 30 | .flutter-plugins 31 | .flutter-plugins-dependencies 32 | .pub-cache/ 33 | .pub/ 34 | /build/ 35 | 36 | # Symbolication related 37 | app.*.symbols 38 | 39 | # Obfuscation related 40 | app.*.map.json 41 | 42 | # Android Studio will place build artifacts here 43 | /android/app/debug 44 | /android/app/profile 45 | /android/app/release 46 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "c23637390482d4cf9598c3ce3f2be31aa7332daf" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 17 | base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 18 | - platform: android 19 | create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 20 | base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 21 | - platform: ios 22 | create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 23 | base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 24 | - platform: linux 25 | create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 26 | base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 27 | - platform: macos 28 | create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 29 | base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 30 | - platform: web 31 | create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 32 | base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 33 | - platform: windows 34 | create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 35 | base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at https://dart.dev/lints. 17 | # 18 | # Instead of disabling a lint rule for the entire project in the 19 | # section below, it can also be suppressed for a single line of code 20 | # or a specific dart file by using the `// ignore: name_of_lint` and 21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 22 | # producing the lint. 23 | rules: 24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 26 | 27 | # Additional information about this file can be found at 28 | # https://dart.dev/guides/language/analysis-options 29 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 54; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 12 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 13 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 14 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 15 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 16 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXContainerItemProxy section */ 20 | 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { 21 | isa = PBXContainerItemProxy; 22 | containerPortal = 97C146E61CF9000F007C117D /* Project object */; 23 | proxyType = 1; 24 | remoteGlobalIDString = 97C146ED1CF9000F007C117D; 25 | remoteInfo = Runner; 26 | }; 27 | /* End PBXContainerItemProxy section */ 28 | 29 | /* Begin PBXCopyFilesBuildPhase section */ 30 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 31 | isa = PBXCopyFilesBuildPhase; 32 | buildActionMask = 2147483647; 33 | dstPath = ""; 34 | dstSubfolderSpec = 10; 35 | files = ( 36 | ); 37 | name = "Embed Frameworks"; 38 | runOnlyForDeploymentPostprocessing = 0; 39 | }; 40 | /* End PBXCopyFilesBuildPhase section */ 41 | 42 | /* Begin PBXFileReference section */ 43 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 44 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 45 | 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 46 | 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 47 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 48 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 49 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 50 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 51 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 52 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 53 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 55 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 56 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 57 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 58 | /* End PBXFileReference section */ 59 | 60 | /* Begin PBXFrameworksBuildPhase section */ 61 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 62 | isa = PBXFrameworksBuildPhase; 63 | buildActionMask = 2147483647; 64 | files = ( 65 | ); 66 | runOnlyForDeploymentPostprocessing = 0; 67 | }; 68 | /* End PBXFrameworksBuildPhase section */ 69 | 70 | /* Begin PBXGroup section */ 71 | 331C8082294A63A400263BE5 /* RunnerTests */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | 331C807B294A618700263BE5 /* RunnerTests.swift */, 75 | ); 76 | path = RunnerTests; 77 | sourceTree = ""; 78 | }; 79 | 9740EEB11CF90186004384FC /* Flutter */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 83 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 84 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 85 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 86 | ); 87 | name = Flutter; 88 | sourceTree = ""; 89 | }; 90 | 97C146E51CF9000F007C117D = { 91 | isa = PBXGroup; 92 | children = ( 93 | 9740EEB11CF90186004384FC /* Flutter */, 94 | 97C146F01CF9000F007C117D /* Runner */, 95 | 97C146EF1CF9000F007C117D /* Products */, 96 | 331C8082294A63A400263BE5 /* RunnerTests */, 97 | ); 98 | sourceTree = ""; 99 | }; 100 | 97C146EF1CF9000F007C117D /* Products */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | 97C146EE1CF9000F007C117D /* Runner.app */, 104 | 331C8081294A63A400263BE5 /* RunnerTests.xctest */, 105 | ); 106 | name = Products; 107 | sourceTree = ""; 108 | }; 109 | 97C146F01CF9000F007C117D /* Runner */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 113 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 114 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 115 | 97C147021CF9000F007C117D /* Info.plist */, 116 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 117 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 118 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 119 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 120 | ); 121 | path = Runner; 122 | sourceTree = ""; 123 | }; 124 | /* End PBXGroup section */ 125 | 126 | /* Begin PBXNativeTarget section */ 127 | 331C8080294A63A400263BE5 /* RunnerTests */ = { 128 | isa = PBXNativeTarget; 129 | buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; 130 | buildPhases = ( 131 | 331C807D294A63A400263BE5 /* Sources */, 132 | 331C807F294A63A400263BE5 /* Resources */, 133 | ); 134 | buildRules = ( 135 | ); 136 | dependencies = ( 137 | 331C8086294A63A400263BE5 /* PBXTargetDependency */, 138 | ); 139 | name = RunnerTests; 140 | productName = RunnerTests; 141 | productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; 142 | productType = "com.apple.product-type.bundle.unit-test"; 143 | }; 144 | 97C146ED1CF9000F007C117D /* Runner */ = { 145 | isa = PBXNativeTarget; 146 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 147 | buildPhases = ( 148 | 9740EEB61CF901F6004384FC /* Run Script */, 149 | 97C146EA1CF9000F007C117D /* Sources */, 150 | 97C146EB1CF9000F007C117D /* Frameworks */, 151 | 97C146EC1CF9000F007C117D /* Resources */, 152 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 153 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 154 | ); 155 | buildRules = ( 156 | ); 157 | dependencies = ( 158 | ); 159 | name = Runner; 160 | productName = Runner; 161 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 162 | productType = "com.apple.product-type.application"; 163 | }; 164 | /* End PBXNativeTarget section */ 165 | 166 | /* Begin PBXProject section */ 167 | 97C146E61CF9000F007C117D /* Project object */ = { 168 | isa = PBXProject; 169 | attributes = { 170 | BuildIndependentTargetsInParallel = YES; 171 | LastUpgradeCheck = 1510; 172 | ORGANIZATIONNAME = ""; 173 | TargetAttributes = { 174 | 331C8080294A63A400263BE5 = { 175 | CreatedOnToolsVersion = 14.0; 176 | TestTargetID = 97C146ED1CF9000F007C117D; 177 | }; 178 | 97C146ED1CF9000F007C117D = { 179 | CreatedOnToolsVersion = 7.3.1; 180 | LastSwiftMigration = 1100; 181 | }; 182 | }; 183 | }; 184 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 185 | compatibilityVersion = "Xcode 9.3"; 186 | developmentRegion = en; 187 | hasScannedForEncodings = 0; 188 | knownRegions = ( 189 | en, 190 | Base, 191 | ); 192 | mainGroup = 97C146E51CF9000F007C117D; 193 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 194 | projectDirPath = ""; 195 | projectRoot = ""; 196 | targets = ( 197 | 97C146ED1CF9000F007C117D /* Runner */, 198 | 331C8080294A63A400263BE5 /* RunnerTests */, 199 | ); 200 | }; 201 | /* End PBXProject section */ 202 | 203 | /* Begin PBXResourcesBuildPhase section */ 204 | 331C807F294A63A400263BE5 /* Resources */ = { 205 | isa = PBXResourcesBuildPhase; 206 | buildActionMask = 2147483647; 207 | files = ( 208 | ); 209 | runOnlyForDeploymentPostprocessing = 0; 210 | }; 211 | 97C146EC1CF9000F007C117D /* Resources */ = { 212 | isa = PBXResourcesBuildPhase; 213 | buildActionMask = 2147483647; 214 | files = ( 215 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 216 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 217 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 218 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 219 | ); 220 | runOnlyForDeploymentPostprocessing = 0; 221 | }; 222 | /* End PBXResourcesBuildPhase section */ 223 | 224 | /* Begin PBXShellScriptBuildPhase section */ 225 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 226 | isa = PBXShellScriptBuildPhase; 227 | alwaysOutOfDate = 1; 228 | buildActionMask = 2147483647; 229 | files = ( 230 | ); 231 | inputPaths = ( 232 | "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", 233 | ); 234 | name = "Thin Binary"; 235 | outputPaths = ( 236 | ); 237 | runOnlyForDeploymentPostprocessing = 0; 238 | shellPath = /bin/sh; 239 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 240 | }; 241 | 9740EEB61CF901F6004384FC /* Run Script */ = { 242 | isa = PBXShellScriptBuildPhase; 243 | alwaysOutOfDate = 1; 244 | buildActionMask = 2147483647; 245 | files = ( 246 | ); 247 | inputPaths = ( 248 | ); 249 | name = "Run Script"; 250 | outputPaths = ( 251 | ); 252 | runOnlyForDeploymentPostprocessing = 0; 253 | shellPath = /bin/sh; 254 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 255 | }; 256 | /* End PBXShellScriptBuildPhase section */ 257 | 258 | /* Begin PBXSourcesBuildPhase section */ 259 | 331C807D294A63A400263BE5 /* Sources */ = { 260 | isa = PBXSourcesBuildPhase; 261 | buildActionMask = 2147483647; 262 | files = ( 263 | 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, 264 | ); 265 | runOnlyForDeploymentPostprocessing = 0; 266 | }; 267 | 97C146EA1CF9000F007C117D /* Sources */ = { 268 | isa = PBXSourcesBuildPhase; 269 | buildActionMask = 2147483647; 270 | files = ( 271 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 272 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 273 | ); 274 | runOnlyForDeploymentPostprocessing = 0; 275 | }; 276 | /* End PBXSourcesBuildPhase section */ 277 | 278 | /* Begin PBXTargetDependency section */ 279 | 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { 280 | isa = PBXTargetDependency; 281 | target = 97C146ED1CF9000F007C117D /* Runner */; 282 | targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; 283 | }; 284 | /* End PBXTargetDependency section */ 285 | 286 | /* Begin PBXVariantGroup section */ 287 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 288 | isa = PBXVariantGroup; 289 | children = ( 290 | 97C146FB1CF9000F007C117D /* Base */, 291 | ); 292 | name = Main.storyboard; 293 | sourceTree = ""; 294 | }; 295 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 296 | isa = PBXVariantGroup; 297 | children = ( 298 | 97C147001CF9000F007C117D /* Base */, 299 | ); 300 | name = LaunchScreen.storyboard; 301 | sourceTree = ""; 302 | }; 303 | /* End PBXVariantGroup section */ 304 | 305 | /* Begin XCBuildConfiguration section */ 306 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 307 | isa = XCBuildConfiguration; 308 | buildSettings = { 309 | ALWAYS_SEARCH_USER_PATHS = NO; 310 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 311 | CLANG_ANALYZER_NONNULL = YES; 312 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 313 | CLANG_CXX_LIBRARY = "libc++"; 314 | CLANG_ENABLE_MODULES = YES; 315 | CLANG_ENABLE_OBJC_ARC = YES; 316 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 317 | CLANG_WARN_BOOL_CONVERSION = YES; 318 | CLANG_WARN_COMMA = YES; 319 | CLANG_WARN_CONSTANT_CONVERSION = YES; 320 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 321 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 322 | CLANG_WARN_EMPTY_BODY = YES; 323 | CLANG_WARN_ENUM_CONVERSION = YES; 324 | CLANG_WARN_INFINITE_RECURSION = YES; 325 | CLANG_WARN_INT_CONVERSION = YES; 326 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 327 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 328 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 329 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 330 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 331 | CLANG_WARN_STRICT_PROTOTYPES = YES; 332 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 333 | CLANG_WARN_UNREACHABLE_CODE = YES; 334 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 335 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 336 | COPY_PHASE_STRIP = NO; 337 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 338 | ENABLE_NS_ASSERTIONS = NO; 339 | ENABLE_STRICT_OBJC_MSGSEND = YES; 340 | ENABLE_USER_SCRIPT_SANDBOXING = NO; 341 | GCC_C_LANGUAGE_STANDARD = gnu99; 342 | GCC_NO_COMMON_BLOCKS = YES; 343 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 344 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 345 | GCC_WARN_UNDECLARED_SELECTOR = YES; 346 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 347 | GCC_WARN_UNUSED_FUNCTION = YES; 348 | GCC_WARN_UNUSED_VARIABLE = YES; 349 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 350 | MTL_ENABLE_DEBUG_INFO = NO; 351 | SDKROOT = iphoneos; 352 | SUPPORTED_PLATFORMS = iphoneos; 353 | TARGETED_DEVICE_FAMILY = "1,2"; 354 | VALIDATE_PRODUCT = YES; 355 | }; 356 | name = Profile; 357 | }; 358 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 359 | isa = XCBuildConfiguration; 360 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 361 | buildSettings = { 362 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 363 | CLANG_ENABLE_MODULES = YES; 364 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 365 | DEVELOPMENT_TEAM = TA8U33SGB8; 366 | ENABLE_BITCODE = NO; 367 | INFOPLIST_FILE = Runner/Info.plist; 368 | LD_RUNPATH_SEARCH_PATHS = ( 369 | "$(inherited)", 370 | "@executable_path/Frameworks", 371 | ); 372 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 373 | PRODUCT_NAME = "$(TARGET_NAME)"; 374 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 375 | SWIFT_VERSION = 5.0; 376 | VERSIONING_SYSTEM = "apple-generic"; 377 | }; 378 | name = Profile; 379 | }; 380 | 331C8088294A63A400263BE5 /* Debug */ = { 381 | isa = XCBuildConfiguration; 382 | buildSettings = { 383 | BUNDLE_LOADER = "$(TEST_HOST)"; 384 | CODE_SIGN_STYLE = Automatic; 385 | CURRENT_PROJECT_VERSION = 1; 386 | GENERATE_INFOPLIST_FILE = YES; 387 | MARKETING_VERSION = 1.0; 388 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; 389 | PRODUCT_NAME = "$(TARGET_NAME)"; 390 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 391 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 392 | SWIFT_VERSION = 5.0; 393 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; 394 | }; 395 | name = Debug; 396 | }; 397 | 331C8089294A63A400263BE5 /* Release */ = { 398 | isa = XCBuildConfiguration; 399 | buildSettings = { 400 | BUNDLE_LOADER = "$(TEST_HOST)"; 401 | CODE_SIGN_STYLE = Automatic; 402 | CURRENT_PROJECT_VERSION = 1; 403 | GENERATE_INFOPLIST_FILE = YES; 404 | MARKETING_VERSION = 1.0; 405 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; 406 | PRODUCT_NAME = "$(TARGET_NAME)"; 407 | SWIFT_VERSION = 5.0; 408 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; 409 | }; 410 | name = Release; 411 | }; 412 | 331C808A294A63A400263BE5 /* Profile */ = { 413 | isa = XCBuildConfiguration; 414 | buildSettings = { 415 | BUNDLE_LOADER = "$(TEST_HOST)"; 416 | CODE_SIGN_STYLE = Automatic; 417 | CURRENT_PROJECT_VERSION = 1; 418 | GENERATE_INFOPLIST_FILE = YES; 419 | MARKETING_VERSION = 1.0; 420 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; 421 | PRODUCT_NAME = "$(TARGET_NAME)"; 422 | SWIFT_VERSION = 5.0; 423 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; 424 | }; 425 | name = Profile; 426 | }; 427 | 97C147031CF9000F007C117D /* Debug */ = { 428 | isa = XCBuildConfiguration; 429 | buildSettings = { 430 | ALWAYS_SEARCH_USER_PATHS = NO; 431 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 432 | CLANG_ANALYZER_NONNULL = YES; 433 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 434 | CLANG_CXX_LIBRARY = "libc++"; 435 | CLANG_ENABLE_MODULES = YES; 436 | CLANG_ENABLE_OBJC_ARC = YES; 437 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 438 | CLANG_WARN_BOOL_CONVERSION = YES; 439 | CLANG_WARN_COMMA = YES; 440 | CLANG_WARN_CONSTANT_CONVERSION = YES; 441 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 442 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 443 | CLANG_WARN_EMPTY_BODY = YES; 444 | CLANG_WARN_ENUM_CONVERSION = YES; 445 | CLANG_WARN_INFINITE_RECURSION = YES; 446 | CLANG_WARN_INT_CONVERSION = YES; 447 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 448 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 449 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 450 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 451 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 452 | CLANG_WARN_STRICT_PROTOTYPES = YES; 453 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 454 | CLANG_WARN_UNREACHABLE_CODE = YES; 455 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 456 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 457 | COPY_PHASE_STRIP = NO; 458 | DEBUG_INFORMATION_FORMAT = dwarf; 459 | ENABLE_STRICT_OBJC_MSGSEND = YES; 460 | ENABLE_TESTABILITY = YES; 461 | ENABLE_USER_SCRIPT_SANDBOXING = NO; 462 | GCC_C_LANGUAGE_STANDARD = gnu99; 463 | GCC_DYNAMIC_NO_PIC = NO; 464 | GCC_NO_COMMON_BLOCKS = YES; 465 | GCC_OPTIMIZATION_LEVEL = 0; 466 | GCC_PREPROCESSOR_DEFINITIONS = ( 467 | "DEBUG=1", 468 | "$(inherited)", 469 | ); 470 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 471 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 472 | GCC_WARN_UNDECLARED_SELECTOR = YES; 473 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 474 | GCC_WARN_UNUSED_FUNCTION = YES; 475 | GCC_WARN_UNUSED_VARIABLE = YES; 476 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 477 | MTL_ENABLE_DEBUG_INFO = YES; 478 | ONLY_ACTIVE_ARCH = YES; 479 | SDKROOT = iphoneos; 480 | TARGETED_DEVICE_FAMILY = "1,2"; 481 | }; 482 | name = Debug; 483 | }; 484 | 97C147041CF9000F007C117D /* Release */ = { 485 | isa = XCBuildConfiguration; 486 | buildSettings = { 487 | ALWAYS_SEARCH_USER_PATHS = NO; 488 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 489 | CLANG_ANALYZER_NONNULL = YES; 490 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 491 | CLANG_CXX_LIBRARY = "libc++"; 492 | CLANG_ENABLE_MODULES = YES; 493 | CLANG_ENABLE_OBJC_ARC = YES; 494 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 495 | CLANG_WARN_BOOL_CONVERSION = YES; 496 | CLANG_WARN_COMMA = YES; 497 | CLANG_WARN_CONSTANT_CONVERSION = YES; 498 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 499 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 500 | CLANG_WARN_EMPTY_BODY = YES; 501 | CLANG_WARN_ENUM_CONVERSION = YES; 502 | CLANG_WARN_INFINITE_RECURSION = YES; 503 | CLANG_WARN_INT_CONVERSION = YES; 504 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 505 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 506 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 507 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 508 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 509 | CLANG_WARN_STRICT_PROTOTYPES = YES; 510 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 511 | CLANG_WARN_UNREACHABLE_CODE = YES; 512 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 513 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 514 | COPY_PHASE_STRIP = NO; 515 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 516 | ENABLE_NS_ASSERTIONS = NO; 517 | ENABLE_STRICT_OBJC_MSGSEND = YES; 518 | ENABLE_USER_SCRIPT_SANDBOXING = NO; 519 | GCC_C_LANGUAGE_STANDARD = gnu99; 520 | GCC_NO_COMMON_BLOCKS = YES; 521 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 522 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 523 | GCC_WARN_UNDECLARED_SELECTOR = YES; 524 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 525 | GCC_WARN_UNUSED_FUNCTION = YES; 526 | GCC_WARN_UNUSED_VARIABLE = YES; 527 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 528 | MTL_ENABLE_DEBUG_INFO = NO; 529 | SDKROOT = iphoneos; 530 | SUPPORTED_PLATFORMS = iphoneos; 531 | SWIFT_COMPILATION_MODE = wholemodule; 532 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 533 | TARGETED_DEVICE_FAMILY = "1,2"; 534 | VALIDATE_PRODUCT = YES; 535 | }; 536 | name = Release; 537 | }; 538 | 97C147061CF9000F007C117D /* Debug */ = { 539 | isa = XCBuildConfiguration; 540 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 541 | buildSettings = { 542 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 543 | CLANG_ENABLE_MODULES = YES; 544 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 545 | DEVELOPMENT_TEAM = TA8U33SGB8; 546 | ENABLE_BITCODE = NO; 547 | INFOPLIST_FILE = Runner/Info.plist; 548 | LD_RUNPATH_SEARCH_PATHS = ( 549 | "$(inherited)", 550 | "@executable_path/Frameworks", 551 | ); 552 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 553 | PRODUCT_NAME = "$(TARGET_NAME)"; 554 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 555 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 556 | SWIFT_VERSION = 5.0; 557 | VERSIONING_SYSTEM = "apple-generic"; 558 | }; 559 | name = Debug; 560 | }; 561 | 97C147071CF9000F007C117D /* Release */ = { 562 | isa = XCBuildConfiguration; 563 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 564 | buildSettings = { 565 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 566 | CLANG_ENABLE_MODULES = YES; 567 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 568 | DEVELOPMENT_TEAM = TA8U33SGB8; 569 | ENABLE_BITCODE = NO; 570 | INFOPLIST_FILE = Runner/Info.plist; 571 | LD_RUNPATH_SEARCH_PATHS = ( 572 | "$(inherited)", 573 | "@executable_path/Frameworks", 574 | ); 575 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 576 | PRODUCT_NAME = "$(TARGET_NAME)"; 577 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 578 | SWIFT_VERSION = 5.0; 579 | VERSIONING_SYSTEM = "apple-generic"; 580 | }; 581 | name = Release; 582 | }; 583 | /* End XCBuildConfiguration section */ 584 | 585 | /* Begin XCConfigurationList section */ 586 | 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { 587 | isa = XCConfigurationList; 588 | buildConfigurations = ( 589 | 331C8088294A63A400263BE5 /* Debug */, 590 | 331C8089294A63A400263BE5 /* Release */, 591 | 331C808A294A63A400263BE5 /* Profile */, 592 | ); 593 | defaultConfigurationIsVisible = 0; 594 | defaultConfigurationName = Release; 595 | }; 596 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 597 | isa = XCConfigurationList; 598 | buildConfigurations = ( 599 | 97C147031CF9000F007C117D /* Debug */, 600 | 97C147041CF9000F007C117D /* Release */, 601 | 249021D3217E4FDB00AE95B9 /* Profile */, 602 | ); 603 | defaultConfigurationIsVisible = 0; 604 | defaultConfigurationName = Release; 605 | }; 606 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 607 | isa = XCConfigurationList; 608 | buildConfigurations = ( 609 | 97C147061CF9000F007C117D /* Debug */, 610 | 97C147071CF9000F007C117D /* Release */, 611 | 249021D4217E4FDB00AE95B9 /* Profile */, 612 | ); 613 | defaultConfigurationIsVisible = 0; 614 | defaultConfigurationName = Release; 615 | }; 616 | /* End XCConfigurationList section */ 617 | }; 618 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 619 | } 620 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 64 | 66 | 72 | 73 | 74 | 75 | 81 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | 4 | @main 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukepighetti/data_widgets/c9805141c6a5a34bb551024674a2b2a018e30627/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukepighetti/data_widgets/c9805141c6a5a34bb551024674a2b2a018e30627/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukepighetti/data_widgets/c9805141c6a5a34bb551024674a2b2a018e30627/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukepighetti/data_widgets/c9805141c6a5a34bb551024674a2b2a018e30627/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukepighetti/data_widgets/c9805141c6a5a34bb551024674a2b2a018e30627/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukepighetti/data_widgets/c9805141c6a5a34bb551024674a2b2a018e30627/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukepighetti/data_widgets/c9805141c6a5a34bb551024674a2b2a018e30627/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukepighetti/data_widgets/c9805141c6a5a34bb551024674a2b2a018e30627/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukepighetti/data_widgets/c9805141c6a5a34bb551024674a2b2a018e30627/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukepighetti/data_widgets/c9805141c6a5a34bb551024674a2b2a018e30627/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukepighetti/data_widgets/c9805141c6a5a34bb551024674a2b2a018e30627/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukepighetti/data_widgets/c9805141c6a5a34bb551024674a2b2a018e30627/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukepighetti/data_widgets/c9805141c6a5a34bb551024674a2b2a018e30627/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukepighetti/data_widgets/c9805141c6a5a34bb551024674a2b2a018e30627/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukepighetti/data_widgets/c9805141c6a5a34bb551024674a2b2a018e30627/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukepighetti/data_widgets/c9805141c6a5a34bb551024674a2b2a018e30627/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukepighetti/data_widgets/c9805141c6a5a34bb551024674a2b2a018e30627/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukepighetti/data_widgets/c9805141c6a5a34bb551024674a2b2a018e30627/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | 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. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Example 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | example 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | CADisableMinimumFrameDurationOnPhone 45 | 46 | UIApplicationSupportsIndirectInputEvents 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /example/lib/data_models.dart: -------------------------------------------------------------------------------- 1 | import 'package:dart_mappable/dart_mappable.dart'; 2 | 3 | part 'data_models.mapper.dart'; 4 | 5 | @MappableClass() 6 | class Group with GroupMappable { 7 | final String title; 8 | final List items; 9 | 10 | Group({required this.title, required this.items}); 11 | } 12 | 13 | @MappableClass() 14 | class GroupItem with GroupItemMappable { 15 | final String title; 16 | 17 | GroupItem({required this.title}); 18 | } 19 | -------------------------------------------------------------------------------- /example/lib/data_models.mapper.dart: -------------------------------------------------------------------------------- 1 | // coverage:ignore-file 2 | // GENERATED CODE - DO NOT MODIFY BY HAND 3 | // ignore_for_file: type=lint 4 | // ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member 5 | // ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter 6 | 7 | part of 'data_models.dart'; 8 | 9 | class GroupMapper extends ClassMapperBase { 10 | GroupMapper._(); 11 | 12 | static GroupMapper? _instance; 13 | static GroupMapper ensureInitialized() { 14 | if (_instance == null) { 15 | MapperContainer.globals.use(_instance = GroupMapper._()); 16 | GroupItemMapper.ensureInitialized(); 17 | } 18 | return _instance!; 19 | } 20 | 21 | @override 22 | final String id = 'Group'; 23 | 24 | static String _$title(Group v) => v.title; 25 | static const Field _f$title = Field('title', _$title); 26 | static List _$items(Group v) => v.items; 27 | static const Field> _f$items = Field('items', _$items); 28 | 29 | @override 30 | final MappableFields fields = const { 31 | #title: _f$title, 32 | #items: _f$items, 33 | }; 34 | 35 | static Group _instantiate(DecodingData data) { 36 | return Group(title: data.dec(_f$title), items: data.dec(_f$items)); 37 | } 38 | 39 | @override 40 | final Function instantiate = _instantiate; 41 | 42 | static Group fromMap(Map map) { 43 | return ensureInitialized().decodeMap(map); 44 | } 45 | 46 | static Group fromJson(String json) { 47 | return ensureInitialized().decodeJson(json); 48 | } 49 | } 50 | 51 | mixin GroupMappable { 52 | String toJson() { 53 | return GroupMapper.ensureInitialized().encodeJson(this as Group); 54 | } 55 | 56 | Map toMap() { 57 | return GroupMapper.ensureInitialized().encodeMap(this as Group); 58 | } 59 | 60 | GroupCopyWith get copyWith => 61 | _GroupCopyWithImpl(this as Group, $identity, $identity); 62 | @override 63 | String toString() { 64 | return GroupMapper.ensureInitialized().stringifyValue(this as Group); 65 | } 66 | 67 | @override 68 | bool operator ==(Object other) { 69 | return GroupMapper.ensureInitialized().equalsValue(this as Group, other); 70 | } 71 | 72 | @override 73 | int get hashCode { 74 | return GroupMapper.ensureInitialized().hashValue(this as Group); 75 | } 76 | } 77 | 78 | extension GroupValueCopy<$R, $Out> on ObjectCopyWith<$R, Group, $Out> { 79 | GroupCopyWith<$R, Group, $Out> get $asGroup => 80 | $base.as((v, t, t2) => _GroupCopyWithImpl<$R, $Out>(v, t, t2)); 81 | } 82 | 83 | abstract class GroupCopyWith<$R, $In extends Group, $Out> 84 | implements ClassCopyWith<$R, $In, $Out> { 85 | ListCopyWith<$R, GroupItem, GroupItemCopyWith<$R, GroupItem, GroupItem>> 86 | get items; 87 | $R call({String? title, List? items}); 88 | GroupCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); 89 | } 90 | 91 | class _GroupCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, Group, $Out> 92 | implements GroupCopyWith<$R, Group, $Out> { 93 | _GroupCopyWithImpl(super.value, super.then, super.then2); 94 | 95 | @override 96 | late final ClassMapperBase $mapper = GroupMapper.ensureInitialized(); 97 | @override 98 | ListCopyWith<$R, GroupItem, GroupItemCopyWith<$R, GroupItem, GroupItem>> 99 | get items => ListCopyWith( 100 | $value.items, (v, t) => v.copyWith.$chain(t), (v) => call(items: v)); 101 | @override 102 | $R call({String? title, List? items}) => $apply(FieldCopyWithData( 103 | {if (title != null) #title: title, if (items != null) #items: items})); 104 | @override 105 | Group $make(CopyWithData data) => Group( 106 | title: data.get(#title, or: $value.title), 107 | items: data.get(#items, or: $value.items)); 108 | 109 | @override 110 | GroupCopyWith<$R2, Group, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t) => 111 | _GroupCopyWithImpl<$R2, $Out2>($value, $cast, t); 112 | } 113 | 114 | class GroupItemMapper extends ClassMapperBase { 115 | GroupItemMapper._(); 116 | 117 | static GroupItemMapper? _instance; 118 | static GroupItemMapper ensureInitialized() { 119 | if (_instance == null) { 120 | MapperContainer.globals.use(_instance = GroupItemMapper._()); 121 | } 122 | return _instance!; 123 | } 124 | 125 | @override 126 | final String id = 'GroupItem'; 127 | 128 | static String _$title(GroupItem v) => v.title; 129 | static const Field _f$title = Field('title', _$title); 130 | 131 | @override 132 | final MappableFields fields = const { 133 | #title: _f$title, 134 | }; 135 | 136 | static GroupItem _instantiate(DecodingData data) { 137 | return GroupItem(title: data.dec(_f$title)); 138 | } 139 | 140 | @override 141 | final Function instantiate = _instantiate; 142 | 143 | static GroupItem fromMap(Map map) { 144 | return ensureInitialized().decodeMap(map); 145 | } 146 | 147 | static GroupItem fromJson(String json) { 148 | return ensureInitialized().decodeJson(json); 149 | } 150 | } 151 | 152 | mixin GroupItemMappable { 153 | String toJson() { 154 | return GroupItemMapper.ensureInitialized() 155 | .encodeJson(this as GroupItem); 156 | } 157 | 158 | Map toMap() { 159 | return GroupItemMapper.ensureInitialized() 160 | .encodeMap(this as GroupItem); 161 | } 162 | 163 | GroupItemCopyWith get copyWith => 164 | _GroupItemCopyWithImpl( 165 | this as GroupItem, $identity, $identity); 166 | @override 167 | String toString() { 168 | return GroupItemMapper.ensureInitialized() 169 | .stringifyValue(this as GroupItem); 170 | } 171 | 172 | @override 173 | bool operator ==(Object other) { 174 | return GroupItemMapper.ensureInitialized() 175 | .equalsValue(this as GroupItem, other); 176 | } 177 | 178 | @override 179 | int get hashCode { 180 | return GroupItemMapper.ensureInitialized().hashValue(this as GroupItem); 181 | } 182 | } 183 | 184 | extension GroupItemValueCopy<$R, $Out> on ObjectCopyWith<$R, GroupItem, $Out> { 185 | GroupItemCopyWith<$R, GroupItem, $Out> get $asGroupItem => 186 | $base.as((v, t, t2) => _GroupItemCopyWithImpl<$R, $Out>(v, t, t2)); 187 | } 188 | 189 | abstract class GroupItemCopyWith<$R, $In extends GroupItem, $Out> 190 | implements ClassCopyWith<$R, $In, $Out> { 191 | $R call({String? title}); 192 | GroupItemCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); 193 | } 194 | 195 | class _GroupItemCopyWithImpl<$R, $Out> 196 | extends ClassCopyWithBase<$R, GroupItem, $Out> 197 | implements GroupItemCopyWith<$R, GroupItem, $Out> { 198 | _GroupItemCopyWithImpl(super.value, super.then, super.then2); 199 | 200 | @override 201 | late final ClassMapperBase $mapper = 202 | GroupItemMapper.ensureInitialized(); 203 | @override 204 | $R call({String? title}) => 205 | $apply(FieldCopyWithData({if (title != null) #title: title})); 206 | @override 207 | GroupItem $make(CopyWithData data) => 208 | GroupItem(title: data.get(#title, or: $value.title)); 209 | 210 | @override 211 | GroupItemCopyWith<$R2, GroupItem, $Out2> $chain<$R2, $Out2>( 212 | Then<$Out2, $R2> t) => 213 | _GroupItemCopyWithImpl<$R2, $Out2>($value, $cast, t); 214 | } 215 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/pages/collection_column_page.dart'; 2 | import 'package:example/pages/int_text_form_field_page.dart'; 3 | import 'package:example/pages/string_text_form_field_page.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | void main() { 7 | runApp(const MyApp()); 8 | } 9 | 10 | class MyApp extends StatelessWidget { 11 | const MyApp({super.key}); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return MaterialApp( 16 | title: 'example', 17 | home: Builder( 18 | builder: (context) { 19 | return Scaffold( 20 | appBar: AppBar(title: Text("data_widgets")), 21 | body: SizedBox( 22 | width: double.maxFinite, 23 | child: Column( 24 | mainAxisAlignment: MainAxisAlignment.center, 25 | crossAxisAlignment: CrossAxisAlignment.center, 26 | children: [ 27 | FilledButton( 28 | child: Text("CollectionColumn"), 29 | onPressed: () => CollectionColumnPage().push(context), 30 | ), 31 | FilledButton( 32 | child: Text("IntTextFormField"), 33 | onPressed: () => IntTextFormFieldPage().push(context), 34 | ), 35 | FilledButton( 36 | child: Text("StringTextFormField"), 37 | onPressed: () => StringTextFormFieldPage().push(context), 38 | ), 39 | ], 40 | ), 41 | ), 42 | ); 43 | }, 44 | ), 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /example/lib/pages/collection_column_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/data_models.dart'; 2 | import 'package:example/utils/extensions.dart'; 3 | import 'package:example/utils/page_mixin.dart'; 4 | import 'package:example/views/example_scaffold.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:data_widgets/data_widgets.dart'; 7 | 8 | class CollectionColumnPage extends StatefulWidget with PageMixin { 9 | const CollectionColumnPage({super.key}); 10 | 11 | @override 12 | State createState() => _CollectionColumnPageState(); 13 | } 14 | 15 | class _CollectionColumnPageState extends State { 16 | var data = [ 17 | Group( 18 | title: "Group A", 19 | items: [GroupItem(title: "Item 1"), GroupItem(title: "Item 2")], 20 | ), 21 | Group(title: "Group B", items: [GroupItem(title: "Item 3")]), 22 | ]; 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return ExampleScaffold( 27 | title: "CollectionColumn", 28 | note: "Try reordering groups and items", 29 | // TODO: allow moving items from one group to another. might need a [GroupedCollectionColumn] 30 | todo: "support moving items between groups", 31 | jsonSource: data.map((e) => e.toMap()).toList(), 32 | body: CollectionColumn( 33 | source: data, 34 | onChange: (update) { 35 | setState(() => data = update); 36 | }, 37 | builder: (_, g) { 38 | return Container( 39 | constraints: BoxConstraints(minHeight: 80), 40 | child: Card( 41 | child: Column( 42 | crossAxisAlignment: CrossAxisAlignment.stretch, 43 | children: [ 44 | ListTile(title: Text(g.title)), 45 | CollectionColumn( 46 | source: g.items, 47 | onChange: (update) { 48 | setState(() { 49 | data = data.replacedWhere( 50 | (e) => e == g, 51 | g.copyWith(items: update), 52 | ); 53 | }); 54 | }, 55 | builder: (_, y) { 56 | return ListTile(title: Text(y.title)); 57 | }, 58 | ), 59 | ], 60 | ), 61 | ), 62 | ); 63 | }, 64 | ), 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /example/lib/pages/int_text_form_field_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/utils/page_mixin.dart'; 2 | import 'package:example/views/example_scaffold.dart'; 3 | import 'package:example/widgets/dismiss_keyboard_icon_button.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:data_widgets/data_widgets.dart'; 6 | 7 | class IntTextFormFieldPage extends StatefulWidget with PageMixin { 8 | const IntTextFormFieldPage({super.key}); 9 | 10 | @override 11 | State createState() => _IntTextFormFieldPageState(); 12 | } 13 | 14 | class _IntTextFormFieldPageState extends State { 15 | var data = 0; 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return ExampleScaffold( 20 | title: "IntTextFormField", 21 | note: "Try opening the keyboard and tapping +", 22 | source: data.toString(), 23 | actions: [DismissKeyboardIconButton()], 24 | body: IntTextFormField( 25 | source: data, 26 | autofocus: true, 27 | onChanged: (update) { 28 | setState(() => data = update); 29 | }, 30 | ), 31 | floatingActionButton: FloatingActionButton( 32 | onPressed: () { 33 | setState(() => data++); 34 | }, 35 | child: Icon(Icons.add), 36 | ), 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /example/lib/pages/string_text_form_field_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/utils/page_mixin.dart'; 2 | import 'package:example/views/example_scaffold.dart'; 3 | import 'package:example/widgets/dismiss_keyboard_icon_button.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:data_widgets/data_widgets.dart'; 6 | 7 | class StringTextFormFieldPage extends StatefulWidget with PageMixin { 8 | const StringTextFormFieldPage({super.key}); 9 | 10 | @override 11 | State createState() => 12 | _StringTextFormFieldPageState(); 13 | } 14 | 15 | class _StringTextFormFieldPageState extends State { 16 | var data = ''; 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return ExampleScaffold( 21 | title: "StringTextFormField", 22 | note: "Try opening the keyboard and tapping +", 23 | source: data.toString(), 24 | actions: [DismissKeyboardIconButton()], 25 | body: StringTextFormField( 26 | source: data, 27 | autofocus: true, 28 | onChanged: (update) { 29 | setState(() => data = update); 30 | }, 31 | ), 32 | floatingActionButton: FloatingActionButton( 33 | onPressed: () { 34 | setState(() => data += 'FAB'); 35 | }, 36 | child: Icon(Icons.add), 37 | ), 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /example/lib/utils/extensions.dart: -------------------------------------------------------------------------------- 1 | extension ListExtensions on List { 2 | List replacedWhere(bool Function(T) test, T replacement) { 3 | return [ 4 | for (final e in this) 5 | if (test(e)) replacement else e, 6 | ]; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /example/lib/utils/page_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | mixin PageMixin on Widget { 4 | Future push(BuildContext context) { 5 | return Navigator.of( 6 | context, 7 | ).push(MaterialPageRoute(builder: (_) => this, fullscreenDialog: true)); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/lib/views/example_scaffold.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_json_view/flutter_json_view.dart'; 5 | 6 | class ExampleScaffold extends StatelessWidget { 7 | const ExampleScaffold({ 8 | super.key, 9 | this.note, 10 | this.todo, 11 | this.source, 12 | this.jsonSource, 13 | this.actions = const [], 14 | required this.title, 15 | required this.body, 16 | this.floatingActionButton, 17 | }); 18 | 19 | final String title; 20 | final List actions; 21 | final String? source; 22 | final Object? jsonSource; 23 | final String? note; 24 | final String? todo; 25 | final Widget body; 26 | final Widget? floatingActionButton; 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | final (source, note, todo) = (this.source, this.note, this.todo); 31 | 32 | return Scaffold( 33 | appBar: AppBar(title: Text(title), actions: actions), 34 | floatingActionButton: floatingActionButton, 35 | body: SafeArea( 36 | child: Padding( 37 | padding: const EdgeInsets.all(8.0), 38 | child: SingleChildScrollView( 39 | child: Column( 40 | spacing: 16, 41 | children: [ 42 | body, 43 | Divider(), 44 | if (jsonSource != null) JsonView.string(jsonEncode(jsonSource)), 45 | if (source != null) Text("source: $source"), 46 | if (note != null) Text(note), 47 | if (todo != null) Text("// TODO: $todo"), 48 | ], 49 | ), 50 | ), 51 | ), 52 | ), 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /example/lib/widgets/dismiss_keyboard_icon_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class DismissKeyboardIconButton extends StatelessWidget { 4 | const DismissKeyboardIconButton({super.key}); 5 | 6 | @override 7 | Widget build(BuildContext context) { 8 | if (MediaQuery.viewInsetsOf(context).bottom == 0) return SizedBox(); 9 | 10 | return IconButton( 11 | icon: Icon(Icons.keyboard_hide), 12 | onPressed: () { 13 | primaryFocus?.unfocus(); 14 | }, 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | _fe_analyzer_shared: 5 | dependency: transitive 6 | description: 7 | name: _fe_analyzer_shared 8 | sha256: "49b9fb8e7e6722ee93252e05fe83eb08fbb2bd9242a375e7afba921a43acd782" 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "81.0.0" 12 | analyzer: 13 | dependency: transitive 14 | description: 15 | name: analyzer 16 | sha256: fab62b609c103ef40024384cd90cb44dc13673df7d21702787100b174feccf01 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "7.4.0" 20 | ansicolor: 21 | dependency: transitive 22 | description: 23 | name: ansicolor 24 | sha256: "50e982d500bc863e1d703448afdbf9e5a72eb48840a4f766fa361ffd6877055f" 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "2.0.3" 28 | args: 29 | dependency: transitive 30 | description: 31 | name: args 32 | sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "2.7.0" 36 | async: 37 | dependency: transitive 38 | description: 39 | name: async 40 | sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "2.12.0" 44 | boolean_selector: 45 | dependency: transitive 46 | description: 47 | name: boolean_selector 48 | sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "2.1.2" 52 | build: 53 | dependency: transitive 54 | description: 55 | name: build 56 | sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 57 | url: "https://pub.dev" 58 | source: hosted 59 | version: "2.4.2" 60 | build_config: 61 | dependency: transitive 62 | description: 63 | name: build_config 64 | sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" 65 | url: "https://pub.dev" 66 | source: hosted 67 | version: "1.1.2" 68 | build_daemon: 69 | dependency: transitive 70 | description: 71 | name: build_daemon 72 | sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa" 73 | url: "https://pub.dev" 74 | source: hosted 75 | version: "4.0.4" 76 | build_resolvers: 77 | dependency: transitive 78 | description: 79 | name: build_resolvers 80 | sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0 81 | url: "https://pub.dev" 82 | source: hosted 83 | version: "2.4.4" 84 | build_runner: 85 | dependency: "direct dev" 86 | description: 87 | name: build_runner 88 | sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99" 89 | url: "https://pub.dev" 90 | source: hosted 91 | version: "2.4.15" 92 | build_runner_core: 93 | dependency: transitive 94 | description: 95 | name: build_runner_core 96 | sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" 97 | url: "https://pub.dev" 98 | source: hosted 99 | version: "8.0.0" 100 | built_collection: 101 | dependency: transitive 102 | description: 103 | name: built_collection 104 | sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" 105 | url: "https://pub.dev" 106 | source: hosted 107 | version: "5.1.1" 108 | built_value: 109 | dependency: transitive 110 | description: 111 | name: built_value 112 | sha256: ea90e81dc4a25a043d9bee692d20ed6d1c4a1662a28c03a96417446c093ed6b4 113 | url: "https://pub.dev" 114 | source: hosted 115 | version: "8.9.5" 116 | characters: 117 | dependency: transitive 118 | description: 119 | name: characters 120 | sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 121 | url: "https://pub.dev" 122 | source: hosted 123 | version: "1.4.0" 124 | checked_yaml: 125 | dependency: transitive 126 | description: 127 | name: checked_yaml 128 | sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff 129 | url: "https://pub.dev" 130 | source: hosted 131 | version: "2.0.3" 132 | clock: 133 | dependency: transitive 134 | description: 135 | name: clock 136 | sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b 137 | url: "https://pub.dev" 138 | source: hosted 139 | version: "1.1.2" 140 | code_builder: 141 | dependency: transitive 142 | description: 143 | name: code_builder 144 | sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" 145 | url: "https://pub.dev" 146 | source: hosted 147 | version: "4.10.1" 148 | collection: 149 | dependency: "direct main" 150 | description: 151 | name: collection 152 | sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" 153 | url: "https://pub.dev" 154 | source: hosted 155 | version: "1.19.1" 156 | convert: 157 | dependency: transitive 158 | description: 159 | name: convert 160 | sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 161 | url: "https://pub.dev" 162 | source: hosted 163 | version: "3.1.2" 164 | crypto: 165 | dependency: transitive 166 | description: 167 | name: crypto 168 | sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" 169 | url: "https://pub.dev" 170 | source: hosted 171 | version: "3.0.6" 172 | cupertino_icons: 173 | dependency: "direct main" 174 | description: 175 | name: cupertino_icons 176 | sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 177 | url: "https://pub.dev" 178 | source: hosted 179 | version: "1.0.8" 180 | dart_mappable: 181 | dependency: "direct main" 182 | description: 183 | name: dart_mappable 184 | sha256: "2255b2c00e328a65fef5a8df2dabfc0dc9c2e518c33a50051a4519b1c7a28c48" 185 | url: "https://pub.dev" 186 | source: hosted 187 | version: "4.5.0" 188 | dart_mappable_builder: 189 | dependency: "direct dev" 190 | description: 191 | name: dart_mappable_builder 192 | sha256: adea8c55aac73c8254aa14a8272b788eb0f72799dd8e4810a9b664ec9b4e353c 193 | url: "https://pub.dev" 194 | source: hosted 195 | version: "4.5.0" 196 | dart_style: 197 | dependency: transitive 198 | description: 199 | name: dart_style 200 | sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac" 201 | url: "https://pub.dev" 202 | source: hosted 203 | version: "3.0.1" 204 | data_widgets: 205 | dependency: "direct main" 206 | description: 207 | path: ".." 208 | relative: true 209 | source: path 210 | version: "0.0.1" 211 | fake_async: 212 | dependency: transitive 213 | description: 214 | name: fake_async 215 | sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" 216 | url: "https://pub.dev" 217 | source: hosted 218 | version: "1.3.2" 219 | file: 220 | dependency: transitive 221 | description: 222 | name: file 223 | sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 224 | url: "https://pub.dev" 225 | source: hosted 226 | version: "7.0.1" 227 | fixnum: 228 | dependency: transitive 229 | description: 230 | name: fixnum 231 | sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be 232 | url: "https://pub.dev" 233 | source: hosted 234 | version: "1.1.1" 235 | flutter: 236 | dependency: "direct main" 237 | description: flutter 238 | source: sdk 239 | version: "0.0.0" 240 | flutter_json_view: 241 | dependency: "direct main" 242 | description: 243 | name: flutter_json_view 244 | sha256: "7acbb6768eccfbc4081bf87bb7ffbe628d6ccfcbdd51b1e713030c44aa3daf49" 245 | url: "https://pub.dev" 246 | source: hosted 247 | version: "1.1.5" 248 | flutter_lints: 249 | dependency: "direct dev" 250 | description: 251 | name: flutter_lints 252 | sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" 253 | url: "https://pub.dev" 254 | source: hosted 255 | version: "5.0.0" 256 | flutter_test: 257 | dependency: "direct dev" 258 | description: flutter 259 | source: sdk 260 | version: "0.0.0" 261 | frontend_server_client: 262 | dependency: transitive 263 | description: 264 | name: frontend_server_client 265 | sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 266 | url: "https://pub.dev" 267 | source: hosted 268 | version: "4.0.0" 269 | glob: 270 | dependency: transitive 271 | description: 272 | name: glob 273 | sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de 274 | url: "https://pub.dev" 275 | source: hosted 276 | version: "2.1.3" 277 | graphs: 278 | dependency: transitive 279 | description: 280 | name: graphs 281 | sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" 282 | url: "https://pub.dev" 283 | source: hosted 284 | version: "2.3.2" 285 | http: 286 | dependency: transitive 287 | description: 288 | name: http 289 | sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f 290 | url: "https://pub.dev" 291 | source: hosted 292 | version: "1.3.0" 293 | http_multi_server: 294 | dependency: transitive 295 | description: 296 | name: http_multi_server 297 | sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 298 | url: "https://pub.dev" 299 | source: hosted 300 | version: "3.2.2" 301 | http_parser: 302 | dependency: transitive 303 | description: 304 | name: http_parser 305 | sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" 306 | url: "https://pub.dev" 307 | source: hosted 308 | version: "4.1.2" 309 | io: 310 | dependency: transitive 311 | description: 312 | name: io 313 | sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b 314 | url: "https://pub.dev" 315 | source: hosted 316 | version: "1.0.5" 317 | js: 318 | dependency: transitive 319 | description: 320 | name: js 321 | sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" 322 | url: "https://pub.dev" 323 | source: hosted 324 | version: "0.7.2" 325 | json_annotation: 326 | dependency: transitive 327 | description: 328 | name: json_annotation 329 | sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" 330 | url: "https://pub.dev" 331 | source: hosted 332 | version: "4.9.0" 333 | leak_tracker: 334 | dependency: transitive 335 | description: 336 | name: leak_tracker 337 | sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec 338 | url: "https://pub.dev" 339 | source: hosted 340 | version: "10.0.8" 341 | leak_tracker_flutter_testing: 342 | dependency: transitive 343 | description: 344 | name: leak_tracker_flutter_testing 345 | sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 346 | url: "https://pub.dev" 347 | source: hosted 348 | version: "3.0.9" 349 | leak_tracker_testing: 350 | dependency: transitive 351 | description: 352 | name: leak_tracker_testing 353 | sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" 354 | url: "https://pub.dev" 355 | source: hosted 356 | version: "3.0.1" 357 | lints: 358 | dependency: transitive 359 | description: 360 | name: lints 361 | sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 362 | url: "https://pub.dev" 363 | source: hosted 364 | version: "5.1.1" 365 | logging: 366 | dependency: transitive 367 | description: 368 | name: logging 369 | sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 370 | url: "https://pub.dev" 371 | source: hosted 372 | version: "1.3.0" 373 | matcher: 374 | dependency: transitive 375 | description: 376 | name: matcher 377 | sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 378 | url: "https://pub.dev" 379 | source: hosted 380 | version: "0.12.17" 381 | material_color_utilities: 382 | dependency: transitive 383 | description: 384 | name: material_color_utilities 385 | sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec 386 | url: "https://pub.dev" 387 | source: hosted 388 | version: "0.11.1" 389 | meta: 390 | dependency: transitive 391 | description: 392 | name: meta 393 | sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c 394 | url: "https://pub.dev" 395 | source: hosted 396 | version: "1.16.0" 397 | mime: 398 | dependency: transitive 399 | description: 400 | name: mime 401 | sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" 402 | url: "https://pub.dev" 403 | source: hosted 404 | version: "2.0.0" 405 | package_config: 406 | dependency: transitive 407 | description: 408 | name: package_config 409 | sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc 410 | url: "https://pub.dev" 411 | source: hosted 412 | version: "2.2.0" 413 | path: 414 | dependency: transitive 415 | description: 416 | name: path 417 | sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" 418 | url: "https://pub.dev" 419 | source: hosted 420 | version: "1.9.1" 421 | pool: 422 | dependency: transitive 423 | description: 424 | name: pool 425 | sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" 426 | url: "https://pub.dev" 427 | source: hosted 428 | version: "1.5.1" 429 | pub_semver: 430 | dependency: transitive 431 | description: 432 | name: pub_semver 433 | sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" 434 | url: "https://pub.dev" 435 | source: hosted 436 | version: "2.2.0" 437 | pubspec_parse: 438 | dependency: transitive 439 | description: 440 | name: pubspec_parse 441 | sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" 442 | url: "https://pub.dev" 443 | source: hosted 444 | version: "1.5.0" 445 | shelf: 446 | dependency: transitive 447 | description: 448 | name: shelf 449 | sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 450 | url: "https://pub.dev" 451 | source: hosted 452 | version: "1.4.2" 453 | shelf_web_socket: 454 | dependency: transitive 455 | description: 456 | name: shelf_web_socket 457 | sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" 458 | url: "https://pub.dev" 459 | source: hosted 460 | version: "3.0.0" 461 | sky_engine: 462 | dependency: transitive 463 | description: flutter 464 | source: sdk 465 | version: "0.0.0" 466 | source_gen: 467 | dependency: transitive 468 | description: 469 | name: source_gen 470 | sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" 471 | url: "https://pub.dev" 472 | source: hosted 473 | version: "2.0.0" 474 | source_span: 475 | dependency: transitive 476 | description: 477 | name: source_span 478 | sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" 479 | url: "https://pub.dev" 480 | source: hosted 481 | version: "1.10.1" 482 | stack_trace: 483 | dependency: transitive 484 | description: 485 | name: stack_trace 486 | sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" 487 | url: "https://pub.dev" 488 | source: hosted 489 | version: "1.12.1" 490 | stream_channel: 491 | dependency: transitive 492 | description: 493 | name: stream_channel 494 | sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" 495 | url: "https://pub.dev" 496 | source: hosted 497 | version: "2.1.4" 498 | stream_transform: 499 | dependency: transitive 500 | description: 501 | name: stream_transform 502 | sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 503 | url: "https://pub.dev" 504 | source: hosted 505 | version: "2.1.1" 506 | string_scanner: 507 | dependency: transitive 508 | description: 509 | name: string_scanner 510 | sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" 511 | url: "https://pub.dev" 512 | source: hosted 513 | version: "1.4.1" 514 | term_glyph: 515 | dependency: transitive 516 | description: 517 | name: term_glyph 518 | sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" 519 | url: "https://pub.dev" 520 | source: hosted 521 | version: "1.2.2" 522 | test_api: 523 | dependency: transitive 524 | description: 525 | name: test_api 526 | sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd 527 | url: "https://pub.dev" 528 | source: hosted 529 | version: "0.7.4" 530 | timing: 531 | dependency: transitive 532 | description: 533 | name: timing 534 | sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" 535 | url: "https://pub.dev" 536 | source: hosted 537 | version: "1.0.2" 538 | type_plus: 539 | dependency: transitive 540 | description: 541 | name: type_plus 542 | sha256: d5d1019471f0d38b91603adb9b5fd4ce7ab903c879d2fbf1a3f80a630a03fcc9 543 | url: "https://pub.dev" 544 | source: hosted 545 | version: "2.1.1" 546 | typed_data: 547 | dependency: transitive 548 | description: 549 | name: typed_data 550 | sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 551 | url: "https://pub.dev" 552 | source: hosted 553 | version: "1.4.0" 554 | vector_math: 555 | dependency: transitive 556 | description: 557 | name: vector_math 558 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 559 | url: "https://pub.dev" 560 | source: hosted 561 | version: "2.1.4" 562 | vm_service: 563 | dependency: transitive 564 | description: 565 | name: vm_service 566 | sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" 567 | url: "https://pub.dev" 568 | source: hosted 569 | version: "14.3.1" 570 | watcher: 571 | dependency: transitive 572 | description: 573 | name: watcher 574 | sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" 575 | url: "https://pub.dev" 576 | source: hosted 577 | version: "1.1.1" 578 | web: 579 | dependency: transitive 580 | description: 581 | name: web 582 | sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" 583 | url: "https://pub.dev" 584 | source: hosted 585 | version: "1.1.1" 586 | web_socket: 587 | dependency: transitive 588 | description: 589 | name: web_socket 590 | sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" 591 | url: "https://pub.dev" 592 | source: hosted 593 | version: "0.1.6" 594 | web_socket_channel: 595 | dependency: transitive 596 | description: 597 | name: web_socket_channel 598 | sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5" 599 | url: "https://pub.dev" 600 | source: hosted 601 | version: "3.0.2" 602 | yaml: 603 | dependency: transitive 604 | description: 605 | name: yaml 606 | sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce 607 | url: "https://pub.dev" 608 | source: hosted 609 | version: "3.1.3" 610 | sdks: 611 | dart: ">=3.7.2 <4.0.0" 612 | flutter: ">=3.18.0-18.0.pre.54" 613 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: example 2 | description: A new Flutter project. 3 | publish_to: none 4 | 5 | version: 1.0.0+1 6 | 7 | environment: 8 | sdk: ^3.7.2 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | collection: ^1.19.1 15 | cupertino_icons: ^1.0.8 16 | dart_mappable: ^4.5.0 17 | flutter_json_view: ^1.1.5 18 | 19 | data_widgets: 20 | path: ../ 21 | 22 | dev_dependencies: 23 | flutter_test: 24 | sdk: flutter 25 | 26 | flutter_lints: ^5.0.0 27 | dart_mappable_builder: ^4.5.0 28 | build_runner: ^2.4.15 29 | 30 | flutter: 31 | uses-material-design: true 32 | -------------------------------------------------------------------------------- /lib/data_widgets.dart: -------------------------------------------------------------------------------- 1 | export 'src/collection_flex.dart'; 2 | export 'src/enum_stack.dart'; 3 | export 'src/typed_text_form_field.dart'; 4 | -------------------------------------------------------------------------------- /lib/src/collection_flex.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CollectionColumn extends StatelessWidget { 4 | const CollectionColumn({ 5 | super.key, 6 | required this.source, 7 | required this.builder, 8 | required this.onChange, 9 | this.actions = const [CollectionAction.move, CollectionAction.delete], 10 | }); 11 | 12 | final List source; 13 | 14 | final Widget Function(BuildContext, T) builder; 15 | 16 | final ValueChanged> onChange; 17 | 18 | final List actions; 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return CollectionFlex( 23 | source: source, 24 | builder: builder, 25 | onChange: onChange, 26 | actions: actions, 27 | direction: Axis.vertical, 28 | ); 29 | } 30 | } 31 | 32 | class CollectionRow extends StatelessWidget { 33 | const CollectionRow({ 34 | super.key, 35 | required this.source, 36 | required this.builder, 37 | required this.onChange, 38 | this.actions = const [CollectionAction.move, CollectionAction.delete], 39 | }); 40 | 41 | final List source; 42 | 43 | final Widget Function(BuildContext, T) builder; 44 | 45 | final ValueChanged> onChange; 46 | 47 | final List actions; 48 | 49 | @override 50 | Widget build(BuildContext context) { 51 | return CollectionFlex( 52 | source: source, 53 | builder: builder, 54 | onChange: onChange, 55 | actions: actions, 56 | direction: Axis.horizontal, 57 | ); 58 | } 59 | } 60 | 61 | // TODO: CollectionListView with onVisibleChanged(List) 62 | 63 | class CollectionFlex extends StatefulWidget { 64 | const CollectionFlex({ 65 | super.key, 66 | required this.source, 67 | required this.builder, 68 | required this.onChange, 69 | required this.direction, 70 | this.keyBuilder, 71 | this.actions = const [CollectionAction.move, CollectionAction.delete], 72 | }); 73 | 74 | final List source; 75 | 76 | final Widget Function(BuildContext, T) builder; 77 | 78 | final ValueChanged> onChange; 79 | 80 | final List actions; 81 | 82 | final Axis direction; 83 | 84 | final Key Function(T)? keyBuilder; 85 | 86 | @override 87 | State createState() => _CollectionFlexState(); 88 | } 89 | 90 | class _CollectionFlexState extends State> { 91 | @override 92 | Widget build(BuildContext context) { 93 | // TODO: implement delete with horizontal swipe action 94 | // TODO: implement add, move, delete animations for when the source changes without human interaction 95 | // TODO: make proxyDecorator no-op and add `bool dragging` to builder 96 | return ReorderableListView( 97 | shrinkWrap: true, 98 | scrollDirection: widget.direction, 99 | physics: NeverScrollableScrollPhysics(), 100 | onReorder: (oldIndex, newIndex) { 101 | widget.onChange(_reorder(widget.source, newIndex, oldIndex)); 102 | }, 103 | 104 | children: [ 105 | for (final x in widget.source) 106 | Builder( 107 | key: widget.keyBuilder?.call(x) ?? ValueKey(x), 108 | builder: (context) { 109 | return widget.builder(context, x); 110 | }, 111 | ), 112 | ], 113 | ); 114 | } 115 | } 116 | 117 | enum CollectionAction { 118 | move, 119 | @Deprecated("not yet supported") 120 | delete, 121 | } 122 | 123 | List _reorder(List source, int newIndex, int oldIndex) { 124 | if (newIndex > oldIndex) { 125 | newIndex -= 1; 126 | } 127 | final items = List.from(source); 128 | final item = items.removeAt(oldIndex); 129 | items.insert(newIndex, item); 130 | return items; 131 | } 132 | -------------------------------------------------------------------------------- /lib/src/enum_stack.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | // TODO: make a swipeable version of this 4 | class EnumStack extends StatefulWidget { 5 | const EnumStack({ 6 | super.key, 7 | required this.value, 8 | required this.values, 9 | required this.builder, 10 | this.slideFraction = 1.0, 11 | this.onEnd, 12 | this.ignorePointerWhileAnimating = true, 13 | }); 14 | 15 | final T value; 16 | final List values; 17 | final Widget Function(BuildContext, T) builder; 18 | final double slideFraction; 19 | final VoidCallback? onEnd; 20 | final bool ignorePointerWhileAnimating; 21 | 22 | @override 23 | State> createState() => _EnumStackState(); 24 | } 25 | 26 | class _EnumStackState extends State> { 27 | final a = {}; 28 | 29 | @override 30 | void didUpdateWidget(covariant EnumStack oldWidget) { 31 | if (oldWidget.value != widget.value) { 32 | setState(() { 33 | a.add(widget.value); 34 | a.add(oldWidget.value); 35 | }); 36 | } 37 | super.didUpdateWidget(oldWidget); 38 | } 39 | 40 | @override 41 | Widget build(BuildContext context) { 42 | final valueIndex = widget.values.indexOf(widget.value); 43 | 44 | return IgnorePointer( 45 | ignoring: a.isNotEmpty && widget.ignorePointerWhileAnimating, 46 | child: Stack( 47 | clipBehavior: Clip.none, 48 | children: [ 49 | for (final (i, x) in widget.values.indexed) 50 | Builder( 51 | builder: (context) { 52 | final d = (i - valueIndex).toDouble(); 53 | var selected = x == widget.value; 54 | var animating = a.contains(x); 55 | var k = widget.slideFraction; 56 | 57 | return AnimatedSlide( 58 | key: ValueKey(x), 59 | offset: Offset((d).clamp(-k, 1).toDouble(), 0), 60 | duration: Duration(milliseconds: 750), 61 | curve: Curves.easeOut, 62 | onEnd: () async { 63 | if (mounted) setState(() => a.remove(x)); 64 | if (a.isEmpty) widget.onEnd?.call(); 65 | }, 66 | child: Visibility( 67 | visible: animating || selected, 68 | child: widget.builder(context, x), 69 | ), 70 | ); 71 | }, 72 | ), 73 | ], 74 | ), 75 | ); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /lib/src/typed_text_form_field.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | 4 | class IntTextFormField extends StatelessWidget { 5 | const IntTextFormField({ 6 | super.key, 7 | required this.source, 8 | required this.onChanged, 9 | this.autofocus = false, 10 | this.focusNode, 11 | this.decoration, 12 | }); 13 | 14 | // TODO: min/max 15 | final bool autofocus; 16 | final FocusNode? focusNode; 17 | final InputDecoration? decoration; 18 | final int source; 19 | final ValueChanged onChanged; 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return TypedTextFormField( 24 | source: source, 25 | onChanged: onChanged, 26 | autofocus: autofocus, 27 | focusNode: focusNode, 28 | spec: TypedTextFormFieldSpec( 29 | serialize: (x) => x.toString(), 30 | deserialize: (x) => int.tryParse(x), 31 | inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'[0-9]'))], 32 | keyboardType: TextInputType.numberWithOptions(decimal: false), 33 | ), 34 | ); 35 | } 36 | } 37 | 38 | class DoubleTextFormField extends StatelessWidget { 39 | const DoubleTextFormField({ 40 | super.key, 41 | required this.source, 42 | required this.onChanged, 43 | this.autofocus = false, 44 | this.focusNode, 45 | this.decoration, 46 | }); 47 | 48 | // TODO: min, max 49 | final double source; 50 | final ValueChanged onChanged; 51 | final bool autofocus; 52 | final FocusNode? focusNode; 53 | final InputDecoration? decoration; 54 | 55 | @override 56 | Widget build(BuildContext context) { 57 | return TypedTextFormField( 58 | source: source, 59 | onChanged: onChanged, 60 | autofocus: autofocus, 61 | focusNode: focusNode, 62 | spec: TypedTextFormFieldSpec( 63 | serialize: (x) => x.toString(), 64 | deserialize: (x) => double.tryParse(x), 65 | inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'[0-9.]'))], 66 | keyboardType: TextInputType.numberWithOptions(decimal: true), 67 | ), 68 | ); 69 | } 70 | } 71 | 72 | class StringTextFormField extends StatelessWidget { 73 | const StringTextFormField({ 74 | super.key, 75 | required this.source, 76 | required this.onChanged, 77 | this.autofocus = false, 78 | this.focusNode, 79 | this.decoration, 80 | this.inputFormatters = const [], 81 | this.keyboardType, 82 | }); 83 | 84 | final bool autofocus; 85 | final FocusNode? focusNode; 86 | final InputDecoration? decoration; 87 | final String source; 88 | final ValueChanged onChanged; 89 | final List inputFormatters; 90 | final TextInputType? keyboardType; 91 | 92 | @override 93 | Widget build(BuildContext context) { 94 | return TypedTextFormField( 95 | source: source, 96 | onChanged: onChanged, 97 | autofocus: autofocus, 98 | focusNode: focusNode, 99 | spec: TypedTextFormFieldSpec( 100 | serialize: (x) => x, 101 | deserialize: (x) => x, 102 | inputFormatters: inputFormatters, 103 | keyboardType: keyboardType, 104 | ), 105 | ); 106 | } 107 | } 108 | 109 | // TODO: EmailTextFormField 110 | // TODO: PasswordTextFormField 111 | // TODO: use placeholder for source until user has edited the value 112 | 113 | class TypedTextFormField extends StatefulWidget { 114 | const TypedTextFormField({ 115 | super.key, 116 | required this.source, 117 | required this.onChanged, 118 | required this.spec, 119 | this.autofocus = false, 120 | this.focusNode, 121 | this.decoration, 122 | }); 123 | 124 | final T source; 125 | final ValueChanged onChanged; 126 | final TypedTextFormFieldSpec spec; 127 | final bool autofocus; 128 | final FocusNode? focusNode; 129 | final InputDecoration? decoration; 130 | 131 | String get _value => spec.serialize(source); 132 | 133 | @override 134 | State> createState() => _TypedTextFormFieldState(); 135 | } 136 | 137 | class _TypedTextFormFieldState extends State> { 138 | late final controller = TextEditingController(text: widget._value); 139 | 140 | @override 141 | void didUpdateWidget(covariant TypedTextFormField oldWidget) { 142 | if (oldWidget._value != widget._value) { 143 | _updateText(widget._value); 144 | } 145 | 146 | super.didUpdateWidget(oldWidget); 147 | } 148 | 149 | @override 150 | Widget build(BuildContext context) { 151 | return TextFormField( 152 | controller: controller, 153 | onChanged: (x) { 154 | final newValue = widget.spec.deserialize(x); 155 | if (newValue != null) widget.onChanged(newValue); 156 | }, 157 | autofocus: widget.autofocus, 158 | focusNode: widget.focusNode, 159 | autocorrect: false, 160 | inputFormatters: widget.spec.inputFormatters, 161 | decoration: widget.decoration, 162 | keyboardType: widget.spec.keyboardType, 163 | ); 164 | } 165 | 166 | void _updateText(String text) { 167 | if (controller.text == text) return; 168 | 169 | controller.value = controller.value.copyWith( 170 | text: text, 171 | selection: TextSelection.fromPosition(TextPosition(offset: text.length)), 172 | composing: TextRange.empty, 173 | ); 174 | } 175 | } 176 | 177 | class TypedTextFormFieldSpec { 178 | final String Function(T) serialize; 179 | final T? Function(String) deserialize; 180 | final List inputFormatters; 181 | final TextInputType? keyboardType; 182 | 183 | TypedTextFormFieldSpec({ 184 | required this.serialize, 185 | required this.deserialize, 186 | required this.inputFormatters, 187 | required this.keyboardType, 188 | }); 189 | } 190 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: data_widgets 2 | description: "A new Flutter package project." 3 | version: 0.0.1 4 | homepage: 5 | 6 | environment: 7 | sdk: ^3.7.2 8 | flutter: ">=1.17.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | dev_dependencies: 15 | flutter_test: 16 | sdk: flutter 17 | flutter_lints: ^5.0.0 18 | 19 | # For information on the generic Dart part of this file, see the 20 | # following page: https://dart.dev/tools/pub/pubspec 21 | 22 | # The following section is specific to Flutter packages. 23 | flutter: 24 | 25 | # To add assets to your package, add an assets section, like this: 26 | # assets: 27 | # - images/a_dot_burr.jpeg 28 | # - images/a_dot_ham.jpeg 29 | # 30 | # For details regarding assets in packages, see 31 | # https://flutter.dev/to/asset-from-package 32 | # 33 | # An image asset can refer to one or more resolution-specific "variants", see 34 | # https://flutter.dev/to/resolution-aware-images 35 | 36 | # To add custom fonts to your package, add a fonts section here, 37 | # in this "flutter" section. Each entry in this list should have a 38 | # "family" key with the font family name, and a "fonts" key with a 39 | # list giving the asset and other descriptors for the font. For 40 | # example: 41 | # fonts: 42 | # - family: Schyler 43 | # fonts: 44 | # - asset: fonts/Schyler-Regular.ttf 45 | # - asset: fonts/Schyler-Italic.ttf 46 | # style: italic 47 | # - family: Trajan Pro 48 | # fonts: 49 | # - asset: fonts/TrajanPro.ttf 50 | # - asset: fonts/TrajanPro_Bold.ttf 51 | # weight: 700 52 | # 53 | # For details regarding fonts in packages, see 54 | # https://flutter.dev/to/font-from-package 55 | -------------------------------------------------------------------------------- /test/src/typed_text_form_field_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:data_widgets/data_widgets.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | 5 | void main() { 6 | group('TypedTextFormField', () { 7 | final ValueNotifier source = ValueNotifier(''); 8 | 9 | Widget buildForString() { 10 | return MaterialApp( 11 | home: Scaffold( 12 | body: ListenableBuilder( 13 | listenable: source, 14 | builder: 15 | (context, _) => TypedTextFormField( 16 | source: source.value, 17 | onChanged: (value) { 18 | source.value = value; 19 | }, 20 | spec: TypedTextFormFieldSpec( 21 | serialize: (x) => x ?? '', 22 | deserialize: (x) => x, 23 | inputFormatters: [], 24 | keyboardType: TextInputType.text, 25 | ), 26 | ), 27 | ), 28 | ), 29 | ); 30 | } 31 | 32 | testWidgets('should show the text', (tester) async { 33 | await tester.pumpWidget(buildForString()); 34 | await tester.enterText(find.byType(TextField), 'test'); 35 | expect(source.value, equals('test')); 36 | }); 37 | 38 | testWidgets('can edit from other cursor positions', (tester) async { 39 | await tester.pumpWidget(buildForString()); 40 | 41 | final controller = 42 | tester.widget(find.byType(TextField)).controller!; 43 | 44 | await tester.showKeyboard(find.byType(TextField)); 45 | 46 | // Simulate a user adding foo to bar from the beginning 47 | tester.testTextInput.updateEditingValue( 48 | controller.value.copyWith( 49 | text: 'foobar', 50 | selection: TextSelection.collapsed(offset: 3), 51 | ), 52 | ); 53 | 54 | await tester.pump(); 55 | 56 | expect(controller.text, equals('foobar')); 57 | expect(controller.selection.start, equals(3)); 58 | expect(controller.selection.end, equals(3)); 59 | expect(source.value, equals('foobar')); 60 | }); 61 | testWidgets('cannot enter invalid composing range', (tester) async { 62 | await tester.pumpWidget(buildForString()); 63 | 64 | await tester.showKeyboard(find.byType(TextField)); 65 | 66 | // Enter text to create an invalid composing range 67 | tester.testTextInput.updateEditingValue( 68 | const TextEditingValue( 69 | text: 'foobar', 70 | selection: TextSelection.collapsed(offset: 2), 71 | composing: TextRange(start: 1, end: 2), 72 | ), 73 | ); 74 | 75 | await tester.pump(); 76 | 77 | // verify that the composing range on the text field is valid 78 | final backingTextField = tester.widget(find.byType(TextField)); 79 | final controller = backingTextField.controller!; 80 | expect(controller.value.composing.start, 1); 81 | expect(controller.value.composing.end, 2); 82 | 83 | // Set the new value externally (not by user) 84 | source.value = ''; 85 | await tester.pump(); 86 | 87 | // Verify that the composing range is cleared 88 | expect(controller.value.composing, TextRange.empty); 89 | 90 | expect(find.byType(TextField), findsOneWidget); 91 | }); 92 | }); 93 | } 94 | --------------------------------------------------------------------------------