├── .github └── workflows │ └── pull-request.yml ├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── dart_test.yaml ├── doc ├── interaction.gif └── search.gif ├── example ├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── data_explorer_example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable-v21 │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── values-night │ │ │ │ └── styles.xml │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── 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 ├── lib │ └── main.dart ├── macos │ ├── Flutter │ │ ├── GeneratedPluginRegistrant.swift │ │ └── ephemeral │ │ │ ├── Flutter-Generated.xcconfig │ │ │ └── flutter_export_environment.sh │ └── Podfile ├── pubspec.lock ├── pubspec.yaml ├── web │ ├── favicon.png │ ├── icons │ │ ├── Icon-192.png │ │ ├── Icon-512.png │ │ ├── Icon-maskable-192.png │ │ └── Icon-maskable-512.png │ ├── index.html │ └── manifest.json └── windows │ ├── .gitignore │ ├── CMakeLists.txt │ ├── flutter │ ├── CMakeLists.txt │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake │ └── runner │ ├── CMakeLists.txt │ ├── Runner.rc │ ├── flutter_window.cpp │ ├── flutter_window.h │ ├── main.cpp │ ├── resource.h │ ├── resources │ └── app_icon.ico │ ├── runner.exe.manifest │ ├── utils.cpp │ ├── utils.h │ ├── win32_window.cpp │ └── win32_window.h ├── lib ├── json_data_explorer.dart └── src │ ├── data_explorer_store.dart │ ├── data_explorer_theme.dart │ └── json_data_explorer.dart ├── pubspec.yaml └── test ├── golden ├── flutter_test_config.dart ├── goldens │ ├── data_explorer │ │ ├── customization.png │ │ └── interaction.png │ ├── indentation │ │ ├── array_indentation.png │ │ ├── class_indentation.png │ │ └── property_indentation.png │ ├── json_attribute.png │ ├── multi_size_test.png │ └── search.png ├── json_attribute_test.dart ├── json_data_explorer_test.dart ├── multi_size_test.dart └── test_data.dart └── unit ├── data_explorer_store_test.dart ├── flatten_tests.dart └── node_view_model_state_test.dart /.github/workflows/pull-request.yml: -------------------------------------------------------------------------------- 1 | name: pull-request 2 | 3 | on: 4 | pull_request: 5 | types: [opened, reopened, synchronize] 6 | 7 | concurrency: 8 | group: ${{ github.head_ref }} 9 | cancel-in-progress: true 10 | 11 | jobs: 12 | ci: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v2 17 | 18 | - name: Install flutter 19 | uses: subosito/flutter-action@master 20 | with: 21 | channel: 'stable' 22 | 23 | - name: Get dependencies 24 | run: flutter pub get 25 | 26 | - name: Run formatter 27 | run: flutter format --set-exit-if-changed . 28 | 29 | - name: Run analyzer 30 | run: flutter analyze . 31 | 32 | - name: Run unit tests 33 | run: flutter test test/unit -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. 25 | /pubspec.lock 26 | **/doc/api/ 27 | .dart_tool/ 28 | .packages 29 | build/ 30 | **/golden/**/failures/ 31 | -------------------------------------------------------------------------------- /.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: b101bfe32f634566e7cb2791a9efe19cf8828b15 8 | channel: beta 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.1.0 2 | 3 | * Initial release. 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, Rows GmbH (rows.com) 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |
4 | Rows 5 |
6 | 7 |
8 |

9 | 10 |

11 | The spreadsheet with superpowers ✨! 12 |
13 |
14 |

15 | 16 |

17 | 18 | 19 |

20 | 21 | 22 | --- 23 | 24 | # JSON Data Explorer 25 | 26 | A highly customizable widget to render and interact with JSON objects. 27 | 28 | 29 |

30 | An animated image of the json widget interaction 32 |      33 | An animated image of the search capabilities 35 |

36 | 37 | ## Features 38 | 39 | - Expand and collapse classes and array nodes. 40 | - Dynamic search with highlight. 41 | - Configurable theme and interactions. 42 | - Configurable data display format. 43 | - Indentation guidelines. 44 | 45 | ## Usage 46 | 47 | The data to be displayed is managed by a store, the `DataExplorerStore`. 48 | In order to use all features from this package you need to register it in 49 | a [Provider](https://pub.dev/packages/provider). 50 | 51 | ```dart 52 | final DataExplorerStore store = DataExplorerStore(); 53 | 54 | /// ... 55 | ChangeNotifierProvider.value( 56 | value: store, 57 | child: 58 | /// ... 59 | ``` 60 | 61 | To load a json object, use `DataExplorerStore.build` nodes method. 62 | 63 | ```dart 64 | store.buildNodes(json.decode(myJson)); 65 | ``` 66 | 67 | To display the data explorer, you can use the `JsonDataExplorer` widget. 68 | The only required parameter is a list of node models, which you can take 69 | from the `DataExplorerStore` after a json was decoded. 70 | 71 | ```dart 72 | Widget build(BuildContext context) { 73 | return Scaffold( 74 | appBar: AppBar( 75 | title: Text(widget.title), 76 | ), 77 | body: SafeArea( 78 | minimum: const EdgeInsets.all(16), 79 | child: ChangeNotifierProvider.value( 80 | value: store, 81 | child: Consumer( 82 | builder: (context, state, child) => JsonDataExplorer( 83 | nodes: state.displayNodes, 84 | ), 85 | ), 86 | ), 87 | ), 88 | ); 89 | } 90 | ``` 91 | 92 | This will display a decoded json using a default theme. 93 | 94 | Check the `/example` app for more information on how to customize the 95 | look and feel of `JsonDataExplorer` widget. 96 | 97 | ### Changing the look and feel 98 | 99 | The `JsonDataExplorer` can be customized to fit different visual requirements. 100 | 101 | #### Themes: 102 | 103 | To change fonts and colors, use a `DataExplorerTheme`: 104 | 105 | ```dart 106 | JsonDataExplorer( 107 | nodes: state.displayNodes, 108 | theme: DataExplorerTheme( 109 | rootKeyTextStyle: GoogleFonts.inconsolata( 110 | color: Colors.black, 111 | fontWeight: FontWeight.bold, 112 | fontSize: 16, 113 | ), 114 | propertyKeyTextStyle: GoogleFonts.inconsolata( 115 | color: Colors.black.withOpacity(0.7), 116 | fontWeight: FontWeight.bold, 117 | fontSize: 16, 118 | ), 119 | keySearchHighlightTextStyle: GoogleFonts.inconsolata( 120 | color: Colors.black, 121 | backgroundColor: const Color(0xFFFFEDAD), 122 | fontWeight: FontWeight.bold, 123 | fontSize: 16, 124 | ), 125 | focusedKeySearchHighlightTextStyle: 126 | GoogleFonts.inconsolata( 127 | color: Colors.black, 128 | backgroundColor: const Color(0xFFF29D0B), 129 | fontWeight: FontWeight.bold, 130 | fontSize: 16, 131 | ), 132 | valueTextStyle: GoogleFonts.inconsolata( 133 | color: const Color(0xFFCA442C), 134 | fontSize: 16, 135 | ), 136 | valueSearchHighlightTextStyle: GoogleFonts.inconsolata( 137 | color: const Color(0xFFCA442C), 138 | backgroundColor: const Color(0xFFFFEDAD), 139 | fontWeight: FontWeight.bold, 140 | fontSize: 16, 141 | ), 142 | focusedValueSearchHighlightTextStyle: 143 | GoogleFonts.inconsolata( 144 | color: Colors.black, 145 | backgroundColor: const Color(0xFFF29D0B), 146 | fontWeight: FontWeight.bold, 147 | fontSize: 16, 148 | ), 149 | indentationLineColor: const Color(0xFFE1E1E1), 150 | highlightColor: const Color(0xFFF1F1F1), 151 | ), 152 | ) 153 | ``` 154 | 155 | #### Formatter: 156 | 157 | Changing the theme is not the only way to customize how the widget looks, 158 | `Formatter` methods can be used to change how key and values are converted 159 | into strings. 160 | 161 | The default behavior to display json property names is `key:`, but this 162 | can be changed with a formatter: 163 | 164 | ```dart 165 | JsonDataExplorer( 166 | nodes: state.displayNodes, 167 | propertyNameFormatter: (name) => '$name ->', 168 | ) 169 | ``` 170 | 171 | Now all property keys are displayed as `key ->`. 172 | 173 | 174 | #### Changing property style based on value: 175 | 176 | Property values `style` and `onTap` can be changed dynamically by using 177 | the `valueStyleBuilder` parameter. It expects a function that receives 178 | the property `dynamic value` and the current `style`, and returns 179 | a `PropertyOverrides`. 180 | 181 | An example is adding interaction to values that contains links: 182 | 183 | ```dart 184 | JsonDataExplorer( 185 | nodes: state.displayNodes, 186 | valueStyleBuilder: (value, style) { 187 | final isUrl = _valueIsUrl(value); 188 | return PropertyOverrides( 189 | style: isUrl 190 | ? style.copyWith( 191 | decoration: TextDecoration.underline, 192 | ) 193 | : style, 194 | onTap: isUrl ? () => _launchUrl(value) : null, 195 | ); 196 | }, 197 | ) 198 | ``` 199 | 200 | Or, folowing the same principle, change how the value looks for specific 201 | value types: 202 | 203 | ```dart 204 | JsonDataExplorer( 205 | nodes: state.displayNodes, 206 | valueStyleBuilder: (value, style) { 207 | if (value is num) { 208 | return PropertyOverrides( 209 | style: style.copyWith( 210 | color: Colors.blue, 211 | ), 212 | ); 213 | } 214 | return PropertyOverrides( 215 | style: style, 216 | ); 217 | }, 218 | ) 219 | ``` 220 | 221 | #### Custom widget components: 222 | 223 | `collapsableToggleBuilder` allow the expand and collapse button that 224 | is displayed on root nodes to be changed. 225 | For example to use a simple implicitly animated widget: 226 | 227 | ```dart 228 | JsonDataExplorer( 229 | nodes: state.displayNodes, 230 | collapsableToggleBuilder: (context, node) => 231 | AnimatedRotation( 232 | turns: node.isCollapsed ? -0.25 : 0, 233 | duration: const Duration(milliseconds: 300), 234 | child: const Icon(Icons.arrow_drop_down), 235 | ), 236 | ) 237 | ``` 238 | 239 | `rootInformationBuilder` builds a widget that is displayed in classes and 240 | arrays root nodes. 241 | As an example, this can be used to display some information about its 242 | children nodes. 243 | 244 | ```dart 245 | JsonDataExplorer( 246 | nodes: state.displayNodes, 247 | rootInformationBuilder: (context, node) => Text( 248 | node.isClass 249 | ? '{${(node.childrenCount)}}' 250 | : '[${node.childrenCount}]', 251 | ), 252 | ) 253 | ``` 254 | 255 | `trailingBuilder` builds a trailing widget in each node. The `NodeViewModelState` 256 | argument allows the widget to react to certain nodes properties. 257 | To build a widget that appears only when a node is currently focused 258 | for example: 259 | 260 | ```dart 261 | JsonDataExplorer( 262 | nodes: state.displayNodes, 263 | trailingBuilder: (context, node) => node.isFocused 264 | ? Text("I'm focused :)") 265 | : const SizedBox(), 266 | ) 267 | ``` 268 | 269 | ### Search 270 | 271 | `DataExplorerStore` provides search functionality using the `search` method. 272 | `JsonDataExplorer` widget already reacts to those state changes and highlights the search results. 273 | Refer to `DataExplorerTheme` to change the looks of search the results. 274 | 275 | 276 | The focused result can be changed by calling the `focusPreviousSearchResult` and `focusNextSearchResult` methods. 277 | 278 | Here is an example of a simple search bar, you can check a full example 279 | in the `example` folder. 280 | 281 | ```dart 282 | Row( 283 | children: [ 284 | Expanded( 285 | child: TextField( 286 | controller: searchController, 287 | onChanged: (term) => dataExplorerStore.search(term), 288 | maxLines: 1, 289 | decoration: const InputDecoration( 290 | hintText: 'Search', 291 | ), 292 | ), 293 | ), 294 | const SizedBox( 295 | width: 8, 296 | ), 297 | IconButton( 298 | onPressed: dataExplorerStore.focusPreviousSearchResult, 299 | icon: const Icon(Icons.arrow_drop_up), 300 | ), 301 | IconButton( 302 | onPressed: dataExplorerStore.focusNextSearchResult, 303 | icon: const Icon(Icons.arrow_drop_down), 304 | ), 305 | ], 306 | ), 307 | ``` 308 | 309 | ### Custom scroll widget 310 | 311 | It is possible to implement your own scrolling by using the `JsonAttribute` 312 | widget to display each node. 313 | 314 | A simple `ListView.builder` looks like this: 315 | 316 | ```dart 317 | ListView.builder( 318 | itemCount: state.displayNodes.length, 319 | itemBuilder: (context, index) => JsonAttribute( 320 | node: state.displayNodes.elementAt(index), 321 | theme: DataExplorerTheme.defaultTheme, 322 | ), 323 | ), 324 | ``` 325 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:rows_lint/analysis_options.yaml 2 | -------------------------------------------------------------------------------- /dart_test.yaml: -------------------------------------------------------------------------------- 1 | tags: 2 | golden: 3 | -------------------------------------------------------------------------------- /doc/interaction.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/doc/interaction.gif -------------------------------------------------------------------------------- /doc/search.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/doc/search.gif -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | -------------------------------------------------------------------------------- /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: b101bfe32f634566e7cb2791a9efe19cf8828b15 8 | channel: beta 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # data_explorer_example 2 | 3 | A new Flutter project. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:rows_lint/analysis_options.yaml 2 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion flutter.compileSdkVersion 30 | ndkVersion flutter.ndkVersion 31 | 32 | compileOptions { 33 | sourceCompatibility JavaVersion.VERSION_1_8 34 | targetCompatibility JavaVersion.VERSION_1_8 35 | } 36 | 37 | kotlinOptions { 38 | jvmTarget = '1.8' 39 | } 40 | 41 | sourceSets { 42 | main.java.srcDirs += 'src/main/kotlin' 43 | } 44 | 45 | defaultConfig { 46 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 47 | applicationId "com.example.data_explorer_example" 48 | minSdkVersion flutter.minSdkVersion 49 | targetSdkVersion flutter.targetSdkVersion 50 | versionCode flutterVersionCode.toInteger() 51 | versionName flutterVersionName 52 | } 53 | 54 | buildTypes { 55 | release { 56 | // TODO: Add your own signing config for the release build. 57 | // Signing with the debug keys for now, so `flutter run --release` works. 58 | signingConfig signingConfigs.debug 59 | } 60 | } 61 | } 62 | 63 | flutter { 64 | source '../..' 65 | } 66 | 67 | dependencies { 68 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 69 | } 70 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 15 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/example/data_explorer_example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.data_explorer_example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.6.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.1.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /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 | 9.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 13 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 14 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 15 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXCopyFilesBuildPhase section */ 19 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 20 | isa = PBXCopyFilesBuildPhase; 21 | buildActionMask = 2147483647; 22 | dstPath = ""; 23 | dstSubfolderSpec = 10; 24 | files = ( 25 | ); 26 | name = "Embed Frameworks"; 27 | runOnlyForDeploymentPostprocessing = 0; 28 | }; 29 | /* End PBXCopyFilesBuildPhase section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 33 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 34 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 35 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 36 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 37 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 38 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 39 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 40 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 42 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 43 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 44 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | /* End PBXFrameworksBuildPhase section */ 56 | 57 | /* Begin PBXGroup section */ 58 | 9740EEB11CF90186004384FC /* Flutter */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 62 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 63 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 64 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 65 | ); 66 | name = Flutter; 67 | sourceTree = ""; 68 | }; 69 | 97C146E51CF9000F007C117D = { 70 | isa = PBXGroup; 71 | children = ( 72 | 9740EEB11CF90186004384FC /* Flutter */, 73 | 97C146F01CF9000F007C117D /* Runner */, 74 | 97C146EF1CF9000F007C117D /* Products */, 75 | ); 76 | sourceTree = ""; 77 | }; 78 | 97C146EF1CF9000F007C117D /* Products */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | 97C146EE1CF9000F007C117D /* Runner.app */, 82 | ); 83 | name = Products; 84 | sourceTree = ""; 85 | }; 86 | 97C146F01CF9000F007C117D /* Runner */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 90 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 91 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 92 | 97C147021CF9000F007C117D /* Info.plist */, 93 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 94 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 95 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 96 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 97 | ); 98 | path = Runner; 99 | sourceTree = ""; 100 | }; 101 | /* End PBXGroup section */ 102 | 103 | /* Begin PBXNativeTarget section */ 104 | 97C146ED1CF9000F007C117D /* Runner */ = { 105 | isa = PBXNativeTarget; 106 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 107 | buildPhases = ( 108 | 9740EEB61CF901F6004384FC /* Run Script */, 109 | 97C146EA1CF9000F007C117D /* Sources */, 110 | 97C146EB1CF9000F007C117D /* Frameworks */, 111 | 97C146EC1CF9000F007C117D /* Resources */, 112 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 113 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 114 | ); 115 | buildRules = ( 116 | ); 117 | dependencies = ( 118 | ); 119 | name = Runner; 120 | productName = Runner; 121 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 122 | productType = "com.apple.product-type.application"; 123 | }; 124 | /* End PBXNativeTarget section */ 125 | 126 | /* Begin PBXProject section */ 127 | 97C146E61CF9000F007C117D /* Project object */ = { 128 | isa = PBXProject; 129 | attributes = { 130 | LastUpgradeCheck = 1300; 131 | ORGANIZATIONNAME = ""; 132 | TargetAttributes = { 133 | 97C146ED1CF9000F007C117D = { 134 | CreatedOnToolsVersion = 7.3.1; 135 | LastSwiftMigration = 1100; 136 | }; 137 | }; 138 | }; 139 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 140 | compatibilityVersion = "Xcode 9.3"; 141 | developmentRegion = en; 142 | hasScannedForEncodings = 0; 143 | knownRegions = ( 144 | en, 145 | Base, 146 | ); 147 | mainGroup = 97C146E51CF9000F007C117D; 148 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 149 | projectDirPath = ""; 150 | projectRoot = ""; 151 | targets = ( 152 | 97C146ED1CF9000F007C117D /* Runner */, 153 | ); 154 | }; 155 | /* End PBXProject section */ 156 | 157 | /* Begin PBXResourcesBuildPhase section */ 158 | 97C146EC1CF9000F007C117D /* Resources */ = { 159 | isa = PBXResourcesBuildPhase; 160 | buildActionMask = 2147483647; 161 | files = ( 162 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 163 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 164 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 165 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 166 | ); 167 | runOnlyForDeploymentPostprocessing = 0; 168 | }; 169 | /* End PBXResourcesBuildPhase section */ 170 | 171 | /* Begin PBXShellScriptBuildPhase section */ 172 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 173 | isa = PBXShellScriptBuildPhase; 174 | buildActionMask = 2147483647; 175 | files = ( 176 | ); 177 | inputPaths = ( 178 | ); 179 | name = "Thin Binary"; 180 | outputPaths = ( 181 | ); 182 | runOnlyForDeploymentPostprocessing = 0; 183 | shellPath = /bin/sh; 184 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 185 | }; 186 | 9740EEB61CF901F6004384FC /* Run Script */ = { 187 | isa = PBXShellScriptBuildPhase; 188 | buildActionMask = 2147483647; 189 | files = ( 190 | ); 191 | inputPaths = ( 192 | ); 193 | name = "Run Script"; 194 | outputPaths = ( 195 | ); 196 | runOnlyForDeploymentPostprocessing = 0; 197 | shellPath = /bin/sh; 198 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 199 | }; 200 | /* End PBXShellScriptBuildPhase section */ 201 | 202 | /* Begin PBXSourcesBuildPhase section */ 203 | 97C146EA1CF9000F007C117D /* Sources */ = { 204 | isa = PBXSourcesBuildPhase; 205 | buildActionMask = 2147483647; 206 | files = ( 207 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 208 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 209 | ); 210 | runOnlyForDeploymentPostprocessing = 0; 211 | }; 212 | /* End PBXSourcesBuildPhase section */ 213 | 214 | /* Begin PBXVariantGroup section */ 215 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 216 | isa = PBXVariantGroup; 217 | children = ( 218 | 97C146FB1CF9000F007C117D /* Base */, 219 | ); 220 | name = Main.storyboard; 221 | sourceTree = ""; 222 | }; 223 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 224 | isa = PBXVariantGroup; 225 | children = ( 226 | 97C147001CF9000F007C117D /* Base */, 227 | ); 228 | name = LaunchScreen.storyboard; 229 | sourceTree = ""; 230 | }; 231 | /* End PBXVariantGroup section */ 232 | 233 | /* Begin XCBuildConfiguration section */ 234 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 235 | isa = XCBuildConfiguration; 236 | buildSettings = { 237 | ALWAYS_SEARCH_USER_PATHS = NO; 238 | CLANG_ANALYZER_NONNULL = YES; 239 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 240 | CLANG_CXX_LIBRARY = "libc++"; 241 | CLANG_ENABLE_MODULES = YES; 242 | CLANG_ENABLE_OBJC_ARC = YES; 243 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 244 | CLANG_WARN_BOOL_CONVERSION = YES; 245 | CLANG_WARN_COMMA = YES; 246 | CLANG_WARN_CONSTANT_CONVERSION = YES; 247 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 248 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 249 | CLANG_WARN_EMPTY_BODY = YES; 250 | CLANG_WARN_ENUM_CONVERSION = YES; 251 | CLANG_WARN_INFINITE_RECURSION = YES; 252 | CLANG_WARN_INT_CONVERSION = YES; 253 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 254 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 255 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 256 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 257 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 258 | CLANG_WARN_STRICT_PROTOTYPES = YES; 259 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 260 | CLANG_WARN_UNREACHABLE_CODE = YES; 261 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 262 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 263 | COPY_PHASE_STRIP = NO; 264 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 265 | ENABLE_NS_ASSERTIONS = NO; 266 | ENABLE_STRICT_OBJC_MSGSEND = YES; 267 | GCC_C_LANGUAGE_STANDARD = gnu99; 268 | GCC_NO_COMMON_BLOCKS = YES; 269 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 270 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 271 | GCC_WARN_UNDECLARED_SELECTOR = YES; 272 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 273 | GCC_WARN_UNUSED_FUNCTION = YES; 274 | GCC_WARN_UNUSED_VARIABLE = YES; 275 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 276 | MTL_ENABLE_DEBUG_INFO = NO; 277 | SDKROOT = iphoneos; 278 | SUPPORTED_PLATFORMS = iphoneos; 279 | TARGETED_DEVICE_FAMILY = "1,2"; 280 | VALIDATE_PRODUCT = YES; 281 | }; 282 | name = Profile; 283 | }; 284 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 285 | isa = XCBuildConfiguration; 286 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 287 | buildSettings = { 288 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 289 | CLANG_ENABLE_MODULES = YES; 290 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 291 | ENABLE_BITCODE = NO; 292 | INFOPLIST_FILE = Runner/Info.plist; 293 | LD_RUNPATH_SEARCH_PATHS = ( 294 | "$(inherited)", 295 | "@executable_path/Frameworks", 296 | ); 297 | PRODUCT_BUNDLE_IDENTIFIER = com.example.dataExplorerExample; 298 | PRODUCT_NAME = "$(TARGET_NAME)"; 299 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 300 | SWIFT_VERSION = 5.0; 301 | VERSIONING_SYSTEM = "apple-generic"; 302 | }; 303 | name = Profile; 304 | }; 305 | 97C147031CF9000F007C117D /* Debug */ = { 306 | isa = XCBuildConfiguration; 307 | buildSettings = { 308 | ALWAYS_SEARCH_USER_PATHS = NO; 309 | CLANG_ANALYZER_NONNULL = YES; 310 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 311 | CLANG_CXX_LIBRARY = "libc++"; 312 | CLANG_ENABLE_MODULES = YES; 313 | CLANG_ENABLE_OBJC_ARC = YES; 314 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 315 | CLANG_WARN_BOOL_CONVERSION = YES; 316 | CLANG_WARN_COMMA = YES; 317 | CLANG_WARN_CONSTANT_CONVERSION = YES; 318 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 319 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 320 | CLANG_WARN_EMPTY_BODY = YES; 321 | CLANG_WARN_ENUM_CONVERSION = YES; 322 | CLANG_WARN_INFINITE_RECURSION = YES; 323 | CLANG_WARN_INT_CONVERSION = YES; 324 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 325 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 326 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 327 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 328 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 329 | CLANG_WARN_STRICT_PROTOTYPES = YES; 330 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 331 | CLANG_WARN_UNREACHABLE_CODE = YES; 332 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 333 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 334 | COPY_PHASE_STRIP = NO; 335 | DEBUG_INFORMATION_FORMAT = dwarf; 336 | ENABLE_STRICT_OBJC_MSGSEND = YES; 337 | ENABLE_TESTABILITY = YES; 338 | GCC_C_LANGUAGE_STANDARD = gnu99; 339 | GCC_DYNAMIC_NO_PIC = NO; 340 | GCC_NO_COMMON_BLOCKS = YES; 341 | GCC_OPTIMIZATION_LEVEL = 0; 342 | GCC_PREPROCESSOR_DEFINITIONS = ( 343 | "DEBUG=1", 344 | "$(inherited)", 345 | ); 346 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 347 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 348 | GCC_WARN_UNDECLARED_SELECTOR = YES; 349 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 350 | GCC_WARN_UNUSED_FUNCTION = YES; 351 | GCC_WARN_UNUSED_VARIABLE = YES; 352 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 353 | MTL_ENABLE_DEBUG_INFO = YES; 354 | ONLY_ACTIVE_ARCH = YES; 355 | SDKROOT = iphoneos; 356 | TARGETED_DEVICE_FAMILY = "1,2"; 357 | }; 358 | name = Debug; 359 | }; 360 | 97C147041CF9000F007C117D /* Release */ = { 361 | isa = XCBuildConfiguration; 362 | buildSettings = { 363 | ALWAYS_SEARCH_USER_PATHS = NO; 364 | CLANG_ANALYZER_NONNULL = YES; 365 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 366 | CLANG_CXX_LIBRARY = "libc++"; 367 | CLANG_ENABLE_MODULES = YES; 368 | CLANG_ENABLE_OBJC_ARC = YES; 369 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 370 | CLANG_WARN_BOOL_CONVERSION = YES; 371 | CLANG_WARN_COMMA = YES; 372 | CLANG_WARN_CONSTANT_CONVERSION = YES; 373 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 374 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 375 | CLANG_WARN_EMPTY_BODY = YES; 376 | CLANG_WARN_ENUM_CONVERSION = YES; 377 | CLANG_WARN_INFINITE_RECURSION = YES; 378 | CLANG_WARN_INT_CONVERSION = YES; 379 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 380 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 381 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 382 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 383 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 384 | CLANG_WARN_STRICT_PROTOTYPES = YES; 385 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 386 | CLANG_WARN_UNREACHABLE_CODE = YES; 387 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 388 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 389 | COPY_PHASE_STRIP = NO; 390 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 391 | ENABLE_NS_ASSERTIONS = NO; 392 | ENABLE_STRICT_OBJC_MSGSEND = YES; 393 | GCC_C_LANGUAGE_STANDARD = gnu99; 394 | GCC_NO_COMMON_BLOCKS = YES; 395 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 396 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 397 | GCC_WARN_UNDECLARED_SELECTOR = YES; 398 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 399 | GCC_WARN_UNUSED_FUNCTION = YES; 400 | GCC_WARN_UNUSED_VARIABLE = YES; 401 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 402 | MTL_ENABLE_DEBUG_INFO = NO; 403 | SDKROOT = iphoneos; 404 | SUPPORTED_PLATFORMS = iphoneos; 405 | SWIFT_COMPILATION_MODE = wholemodule; 406 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 407 | TARGETED_DEVICE_FAMILY = "1,2"; 408 | VALIDATE_PRODUCT = YES; 409 | }; 410 | name = Release; 411 | }; 412 | 97C147061CF9000F007C117D /* Debug */ = { 413 | isa = XCBuildConfiguration; 414 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 415 | buildSettings = { 416 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 417 | CLANG_ENABLE_MODULES = YES; 418 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 419 | ENABLE_BITCODE = NO; 420 | INFOPLIST_FILE = Runner/Info.plist; 421 | LD_RUNPATH_SEARCH_PATHS = ( 422 | "$(inherited)", 423 | "@executable_path/Frameworks", 424 | ); 425 | PRODUCT_BUNDLE_IDENTIFIER = com.example.dataExplorerExample; 426 | PRODUCT_NAME = "$(TARGET_NAME)"; 427 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 428 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 429 | SWIFT_VERSION = 5.0; 430 | VERSIONING_SYSTEM = "apple-generic"; 431 | }; 432 | name = Debug; 433 | }; 434 | 97C147071CF9000F007C117D /* Release */ = { 435 | isa = XCBuildConfiguration; 436 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 437 | buildSettings = { 438 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 439 | CLANG_ENABLE_MODULES = YES; 440 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 441 | ENABLE_BITCODE = NO; 442 | INFOPLIST_FILE = Runner/Info.plist; 443 | LD_RUNPATH_SEARCH_PATHS = ( 444 | "$(inherited)", 445 | "@executable_path/Frameworks", 446 | ); 447 | PRODUCT_BUNDLE_IDENTIFIER = com.example.dataExplorerExample; 448 | PRODUCT_NAME = "$(TARGET_NAME)"; 449 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 450 | SWIFT_VERSION = 5.0; 451 | VERSIONING_SYSTEM = "apple-generic"; 452 | }; 453 | name = Release; 454 | }; 455 | /* End XCBuildConfiguration section */ 456 | 457 | /* Begin XCConfigurationList section */ 458 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 459 | isa = XCConfigurationList; 460 | buildConfigurations = ( 461 | 97C147031CF9000F007C117D /* Debug */, 462 | 97C147041CF9000F007C117D /* Release */, 463 | 249021D3217E4FDB00AE95B9 /* Profile */, 464 | ); 465 | defaultConfigurationIsVisible = 0; 466 | defaultConfigurationName = Release; 467 | }; 468 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 469 | isa = XCConfigurationList; 470 | buildConfigurations = ( 471 | 97C147061CF9000F007C117D /* Debug */, 472 | 97C147071CF9000F007C117D /* Release */, 473 | 249021D4217E4FDB00AE95B9 /* Profile */, 474 | ); 475 | defaultConfigurationIsVisible = 0; 476 | defaultConfigurationName = Release; 477 | }; 478 | /* End XCConfigurationList section */ 479 | }; 480 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 481 | } 482 | -------------------------------------------------------------------------------- /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 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /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 UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 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/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/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/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/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/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/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/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/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/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/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/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/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/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/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/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/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/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/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/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/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/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/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/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/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/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/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/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/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/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/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/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/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 | Data Explorer Example 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | data_explorer_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 | UIViewControllerBasedStatusBarAppearance 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:google_fonts/google_fonts.dart'; 5 | import 'package:http/http.dart' as http; 6 | import 'package:json_data_explorer/json_data_explorer.dart'; 7 | import 'package:provider/provider.dart'; 8 | import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; 9 | import 'package:url_launcher/url_launcher.dart'; 10 | 11 | void main() => runApp(const MyApp()); 12 | 13 | class MyApp extends StatelessWidget { 14 | const MyApp({Key? key}) : super(key: key); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return MaterialApp( 19 | title: 'Json Data Explorer', 20 | theme: ThemeData( 21 | primarySwatch: Colors.blue, 22 | ), 23 | home: const MyHomePage(), 24 | ); 25 | } 26 | } 27 | 28 | class MyHomePage extends StatelessWidget { 29 | const MyHomePage({Key? key}) : super(key: key); 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return Scaffold( 34 | appBar: AppBar( 35 | title: const Text('Test Data Explorer'), 36 | ), 37 | body: ListView( 38 | padding: const EdgeInsets.all(16), 39 | children: [ 40 | Text( 41 | 'Small JSON', 42 | style: Theme.of(context).textTheme.headline6, 43 | ), 44 | const _OpenJsonButton( 45 | title: 'ISS current location', 46 | url: 'http://api.open-notify.org/iss-now.json', 47 | padding: EdgeInsets.symmetric(vertical: 8.0), 48 | ), 49 | Text( 50 | 'Medium JSON', 51 | style: Theme.of(context).textTheme.headline6, 52 | ), 53 | const _OpenJsonButton( 54 | title: 'Nobel prizes country', 55 | url: 'http://api.nobelprize.org/v1/country.json', 56 | padding: EdgeInsets.symmetric(vertical: 8.0), 57 | ), 58 | const _OpenJsonButton( 59 | title: 'Australia ABC Local Stations', 60 | url: 61 | 'https://data.gov.au/geoserver/abc-local-stations/wfs?request=GetFeature&typeName=ckan_d534c0e9_a9bf_487b_ac8f_b7877a09d162&outputFormat=json', 62 | ), 63 | Text( 64 | 'Large JSON', 65 | style: Theme.of(context).textTheme.headline6, 66 | ), 67 | const _OpenJsonButton( 68 | title: 'Pokémon', 69 | url: 'https://pokeapi.co/api/v2/pokemon/?offset=0&limit=2000', 70 | padding: EdgeInsets.symmetric(vertical: 8.0), 71 | ), 72 | const _OpenJsonButton( 73 | title: 'Earth Meteorite Landings', 74 | url: 'https://data.nasa.gov/resource/y77d-th95.json', 75 | ), 76 | const _OpenJsonButton( 77 | title: 'Reddit r/all', 78 | url: 'https://www.reddit.com/r/all.json', 79 | ), 80 | Text( 81 | 'Exploding JSON', 82 | style: Theme.of(context).textTheme.headline6, 83 | ), 84 | const _OpenJsonButton( 85 | title: '25MB GitHub Json', 86 | url: 87 | 'https://raw.githubusercontent.com/json-iterator/test-data/master/large-file.json', 88 | padding: EdgeInsets.only(top: 8.0, bottom: 32.0), 89 | ), 90 | Text( 91 | 'More datasets at https://awesomeopensource.com/project/jdorfman/awesome-json-datasets', 92 | style: Theme.of(context).textTheme.caption, 93 | ), 94 | ], 95 | ), 96 | ); 97 | } 98 | } 99 | 100 | class DataExplorerPage extends StatefulWidget { 101 | final String jsonUrl; 102 | final String title; 103 | 104 | const DataExplorerPage({ 105 | Key? key, 106 | required this.jsonUrl, 107 | required this.title, 108 | }) : super(key: key); 109 | 110 | @override 111 | _DataExplorerPageState createState() => _DataExplorerPageState(); 112 | } 113 | 114 | class _DataExplorerPageState extends State { 115 | final searchController = TextEditingController(); 116 | final itemScrollController = ItemScrollController(); 117 | final DataExplorerStore store = DataExplorerStore(); 118 | 119 | @override 120 | void initState() { 121 | _loadJsonDataFrom(widget.jsonUrl); 122 | super.initState(); 123 | } 124 | 125 | @override 126 | Widget build(BuildContext context) { 127 | return Scaffold( 128 | appBar: AppBar( 129 | title: Text(widget.title), 130 | ), 131 | body: SafeArea( 132 | minimum: const EdgeInsets.all(16), 133 | child: ChangeNotifierProvider.value( 134 | value: store, 135 | child: Consumer( 136 | builder: (context, state, child) => Column( 137 | crossAxisAlignment: CrossAxisAlignment.start, 138 | children: [ 139 | Row( 140 | children: [ 141 | Expanded( 142 | child: TextField( 143 | controller: searchController, 144 | 145 | /// Delegates the search to [DataExplorerStore] when 146 | /// the text field changes. 147 | onChanged: (term) => state.search(term), 148 | decoration: const InputDecoration( 149 | hintText: 'Search', 150 | ), 151 | ), 152 | ), 153 | const SizedBox( 154 | width: 8, 155 | ), 156 | if (state.searchResults.isNotEmpty) 157 | Text(_searchFocusText()), 158 | if (state.searchResults.isNotEmpty) 159 | IconButton( 160 | onPressed: () { 161 | store.focusPreviousSearchResult(); 162 | _scrollToSearchMatch(); 163 | }, 164 | icon: const Icon(Icons.arrow_drop_up), 165 | ), 166 | if (state.searchResults.isNotEmpty) 167 | IconButton( 168 | onPressed: () { 169 | store.focusNextSearchResult(); 170 | _scrollToSearchMatch(); 171 | }, 172 | icon: const Icon(Icons.arrow_drop_down), 173 | ), 174 | ], 175 | ), 176 | const SizedBox( 177 | height: 16.0, 178 | ), 179 | Row( 180 | children: [ 181 | TextButton( 182 | onPressed: 183 | state.areAllExpanded() ? null : state.expandAll, 184 | child: const Text('Expand All'), 185 | ), 186 | const SizedBox( 187 | width: 8.0, 188 | ), 189 | TextButton( 190 | onPressed: 191 | state.areAllCollapsed() ? null : state.collapseAll, 192 | child: const Text('Collapse All'), 193 | ), 194 | ], 195 | ), 196 | const SizedBox( 197 | height: 16.0, 198 | ), 199 | Expanded( 200 | child: JsonDataExplorer( 201 | nodes: state.displayNodes, 202 | itemScrollController: itemScrollController, 203 | itemSpacing: 4, 204 | 205 | /// Builds a widget after each root node displaying the 206 | /// number of children nodes that it has. Displays `{x}` 207 | /// if it is a class or `[x]` in case of arrays. 208 | rootInformationBuilder: (context, node) => DecoratedBox( 209 | decoration: const BoxDecoration( 210 | color: Color(0x80E1E1E1), 211 | borderRadius: BorderRadius.all(Radius.circular(2)), 212 | ), 213 | child: Padding( 214 | padding: const EdgeInsets.symmetric( 215 | horizontal: 4, 216 | vertical: 2, 217 | ), 218 | child: Text( 219 | node.isClass 220 | ? '{${node.childrenCount}}' 221 | : '[${node.childrenCount}]', 222 | style: GoogleFonts.inconsolata( 223 | fontSize: 12, 224 | color: const Color(0xFF6F6F6F), 225 | ), 226 | ), 227 | ), 228 | ), 229 | 230 | /// Build an animated collapse/expand indicator. Implicitly 231 | /// animates the indicator when 232 | /// [NodeViewModelState.isCollapsed] changes. 233 | collapsableToggleBuilder: (context, node) => 234 | AnimatedRotation( 235 | turns: node.isCollapsed ? -0.25 : 0, 236 | duration: const Duration(milliseconds: 300), 237 | child: const Icon(Icons.arrow_drop_down), 238 | ), 239 | 240 | /// Builds a trailing widget that copies the node key: value 241 | /// 242 | /// Uses [NodeViewModelState.isFocused] to display the 243 | /// widget only in focused widgets. 244 | trailingBuilder: (context, node) => node.isFocused 245 | ? IconButton( 246 | padding: EdgeInsets.zero, 247 | constraints: const BoxConstraints(maxHeight: 18), 248 | icon: const Icon( 249 | Icons.copy, 250 | size: 18, 251 | ), 252 | onPressed: () => _printNode(node), 253 | ) 254 | : const SizedBox(), 255 | 256 | /// Creates a custom format for classes and array names. 257 | rootNameFormatter: (dynamic name) => '$name', 258 | 259 | /// Dynamically changes the property value style and 260 | /// interaction when an URL is detected. 261 | valueStyleBuilder: (dynamic value, style) { 262 | final isUrl = _valueIsUrl(value); 263 | return PropertyOverrides( 264 | style: isUrl 265 | ? style.copyWith( 266 | decoration: TextDecoration.underline, 267 | ) 268 | : style, 269 | onTap: isUrl ? () => _launchUrl(value as String) : null, 270 | ); 271 | }, 272 | 273 | /// Theme definitions of the json data explorer 274 | theme: DataExplorerTheme( 275 | rootKeyTextStyle: GoogleFonts.inconsolata( 276 | color: Colors.black, 277 | fontWeight: FontWeight.bold, 278 | fontSize: 16, 279 | ), 280 | propertyKeyTextStyle: GoogleFonts.inconsolata( 281 | color: Colors.black.withOpacity(0.7), 282 | fontWeight: FontWeight.bold, 283 | fontSize: 16, 284 | ), 285 | keySearchHighlightTextStyle: GoogleFonts.inconsolata( 286 | color: Colors.black, 287 | backgroundColor: const Color(0xFFFFEDAD), 288 | fontWeight: FontWeight.bold, 289 | fontSize: 16, 290 | ), 291 | focusedKeySearchHighlightTextStyle: 292 | GoogleFonts.inconsolata( 293 | color: Colors.black, 294 | backgroundColor: const Color(0xFFF29D0B), 295 | fontWeight: FontWeight.bold, 296 | fontSize: 16, 297 | ), 298 | valueTextStyle: GoogleFonts.inconsolata( 299 | color: const Color(0xFFCA442C), 300 | fontSize: 16, 301 | ), 302 | valueSearchHighlightTextStyle: GoogleFonts.inconsolata( 303 | color: const Color(0xFFCA442C), 304 | backgroundColor: const Color(0xFFFFEDAD), 305 | fontWeight: FontWeight.bold, 306 | fontSize: 16, 307 | ), 308 | focusedValueSearchHighlightTextStyle: 309 | GoogleFonts.inconsolata( 310 | color: Colors.black, 311 | backgroundColor: const Color(0xFFF29D0B), 312 | fontWeight: FontWeight.bold, 313 | fontSize: 16, 314 | ), 315 | indentationLineColor: const Color(0xFFE1E1E1), 316 | highlightColor: const Color(0xFFF1F1F1), 317 | ), 318 | ), 319 | ), 320 | ], 321 | ), 322 | ), 323 | ), 324 | ), 325 | ); 326 | } 327 | 328 | String _searchFocusText() => 329 | '${store.focusedSearchResultIndex + 1} of ${store.searchResults.length}'; 330 | 331 | Future _loadJsonDataFrom(String url) async { 332 | debugPrint('Calling Json API'); 333 | final data = await http.read(Uri.parse(url)); 334 | debugPrint('Done!'); 335 | final dynamic decoded = json.decode(data); 336 | store.buildNodes(decoded, areAllCollapsed: true); 337 | } 338 | 339 | void _printNode(NodeViewModelState node) { 340 | if (node.isRoot) { 341 | final value = node.isClass ? 'class' : 'array'; 342 | debugPrint('${node.key}: $value'); 343 | return; 344 | } 345 | debugPrint('${node.key}: ${node.value}'); 346 | } 347 | 348 | void _scrollToSearchMatch() { 349 | final index = store.displayNodes.indexOf(store.focusedSearchResult.node); 350 | if (index != -1) { 351 | itemScrollController.scrollTo( 352 | index: index, 353 | duration: const Duration(milliseconds: 300), 354 | curve: Curves.easeInOutCubic, 355 | ); 356 | } 357 | } 358 | 359 | bool _valueIsUrl(dynamic value) { 360 | if (value is String) { 361 | return Uri.tryParse(value)?.hasAbsolutePath ?? false; 362 | } 363 | return false; 364 | } 365 | 366 | Future _launchUrl(String url) { 367 | return launch(url); 368 | } 369 | 370 | @override 371 | void dispose() { 372 | searchController.dispose(); 373 | super.dispose(); 374 | } 375 | } 376 | 377 | /// A button that navigates to the data explorer page on pressed. 378 | class _OpenJsonButton extends StatelessWidget { 379 | final String url; 380 | final String title; 381 | final EdgeInsets padding; 382 | 383 | const _OpenJsonButton({ 384 | Key? key, 385 | required this.url, 386 | required this.title, 387 | this.padding = const EdgeInsets.only(bottom: 8.0), 388 | }) : super(key: key); 389 | 390 | @override 391 | Widget build(BuildContext context) => Padding( 392 | padding: padding, 393 | child: ElevatedButton( 394 | child: Text(title), 395 | onPressed: () => Navigator.of(context).push( 396 | MaterialPageRoute( 397 | builder: (ctx) => DataExplorerPage( 398 | jsonUrl: url, 399 | title: title, 400 | ), 401 | ), 402 | ), 403 | ), 404 | ); 405 | } 406 | -------------------------------------------------------------------------------- /example/macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import path_provider_macos 9 | import url_launcher_macos 10 | 11 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 12 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 13 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) 14 | } 15 | -------------------------------------------------------------------------------- /example/macos/Flutter/ephemeral/Flutter-Generated.xcconfig: -------------------------------------------------------------------------------- 1 | // This is a generated file; do not edit or check into version control. 2 | FLUTTER_ROOT=/Users/rows/src/flutter 3 | FLUTTER_APPLICATION_PATH=/Users/rows/Developer/flutter/json-data-explorer/example 4 | COCOAPODS_PARALLEL_CODE_SIGN=true 5 | FLUTTER_BUILD_DIR=build 6 | FLUTTER_BUILD_NAME=1.0.0 7 | FLUTTER_BUILD_NUMBER=1 8 | EXCLUDED_ARCHS=arm64 9 | DART_OBFUSCATION=false 10 | TRACK_WIDGET_CREATION=false 11 | TREE_SHAKE_ICONS=false 12 | PACKAGE_CONFIG=.packages 13 | -------------------------------------------------------------------------------- /example/macos/Flutter/ephemeral/flutter_export_environment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This is a generated file; do not edit or check into version control. 3 | export "FLUTTER_ROOT=/Users/rows/src/flutter" 4 | export "FLUTTER_APPLICATION_PATH=/Users/rows/Developer/flutter/json-data-explorer/example" 5 | export "COCOAPODS_PARALLEL_CODE_SIGN=true" 6 | export "FLUTTER_BUILD_DIR=build" 7 | export "FLUTTER_BUILD_NAME=1.0.0" 8 | export "FLUTTER_BUILD_NUMBER=1" 9 | export "EXCLUDED_ARCHS=arm64" 10 | export "DART_OBFUSCATION=false" 11 | export "TRACK_WIDGET_CREATION=false" 12 | export "TREE_SHAKE_ICONS=false" 13 | export "PACKAGE_CONFIG=.packages" 14 | -------------------------------------------------------------------------------- /example/macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.11' 2 | 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 | 6 | project 'Runner', { 7 | 'Debug' => :debug, 8 | 'Profile' => :release, 9 | 'Release' => :release, 10 | } 11 | 12 | def flutter_root 13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) 14 | unless File.exist?(generated_xcode_build_settings_path) 15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" 16 | end 17 | 18 | File.foreach(generated_xcode_build_settings_path) do |line| 19 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 20 | return matches[1].strip if matches 21 | end 22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" 23 | end 24 | 25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 26 | 27 | flutter_macos_podfile_setup 28 | 29 | target 'Runner' do 30 | use_frameworks! 31 | use_modular_headers! 32 | 33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) 34 | end 35 | 36 | post_install do |installer| 37 | installer.pods_project.targets.each do |target| 38 | flutter_additional_macos_build_settings(target) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.8.2" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.2.0" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.3.1" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.0" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.15.0" 46 | crypto: 47 | dependency: transitive 48 | description: 49 | name: crypto 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "3.0.1" 53 | effective_dart: 54 | dependency: transitive 55 | description: 56 | name: effective_dart 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "1.3.2" 60 | fake_async: 61 | dependency: transitive 62 | description: 63 | name: fake_async 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "1.2.0" 67 | ffi: 68 | dependency: transitive 69 | description: 70 | name: ffi 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "1.1.2" 74 | file: 75 | dependency: transitive 76 | description: 77 | name: file 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "6.1.2" 81 | flutter: 82 | dependency: "direct main" 83 | description: flutter 84 | source: sdk 85 | version: "0.0.0" 86 | flutter_test: 87 | dependency: "direct dev" 88 | description: flutter 89 | source: sdk 90 | version: "0.0.0" 91 | flutter_web_plugins: 92 | dependency: transitive 93 | description: flutter 94 | source: sdk 95 | version: "0.0.0" 96 | google_fonts: 97 | dependency: "direct main" 98 | description: 99 | name: google_fonts 100 | url: "https://pub.dartlang.org" 101 | source: hosted 102 | version: "2.3.1" 103 | http: 104 | dependency: "direct main" 105 | description: 106 | name: http 107 | url: "https://pub.dartlang.org" 108 | source: hosted 109 | version: "0.13.4" 110 | http_parser: 111 | dependency: transitive 112 | description: 113 | name: http_parser 114 | url: "https://pub.dartlang.org" 115 | source: hosted 116 | version: "4.0.0" 117 | js: 118 | dependency: transitive 119 | description: 120 | name: js 121 | url: "https://pub.dartlang.org" 122 | source: hosted 123 | version: "0.6.3" 124 | json_data_explorer: 125 | dependency: "direct main" 126 | description: 127 | path: ".." 128 | relative: true 129 | source: path 130 | version: "0.0.1" 131 | matcher: 132 | dependency: transitive 133 | description: 134 | name: matcher 135 | url: "https://pub.dartlang.org" 136 | source: hosted 137 | version: "0.12.11" 138 | material_color_utilities: 139 | dependency: transitive 140 | description: 141 | name: material_color_utilities 142 | url: "https://pub.dartlang.org" 143 | source: hosted 144 | version: "0.1.3" 145 | meta: 146 | dependency: transitive 147 | description: 148 | name: meta 149 | url: "https://pub.dartlang.org" 150 | source: hosted 151 | version: "1.7.0" 152 | nested: 153 | dependency: transitive 154 | description: 155 | name: nested 156 | url: "https://pub.dartlang.org" 157 | source: hosted 158 | version: "1.0.0" 159 | path: 160 | dependency: transitive 161 | description: 162 | name: path 163 | url: "https://pub.dartlang.org" 164 | source: hosted 165 | version: "1.8.0" 166 | path_provider: 167 | dependency: transitive 168 | description: 169 | name: path_provider 170 | url: "https://pub.dartlang.org" 171 | source: hosted 172 | version: "2.0.9" 173 | path_provider_android: 174 | dependency: transitive 175 | description: 176 | name: path_provider_android 177 | url: "https://pub.dartlang.org" 178 | source: hosted 179 | version: "2.0.12" 180 | path_provider_ios: 181 | dependency: transitive 182 | description: 183 | name: path_provider_ios 184 | url: "https://pub.dartlang.org" 185 | source: hosted 186 | version: "2.0.8" 187 | path_provider_linux: 188 | dependency: transitive 189 | description: 190 | name: path_provider_linux 191 | url: "https://pub.dartlang.org" 192 | source: hosted 193 | version: "2.1.5" 194 | path_provider_macos: 195 | dependency: transitive 196 | description: 197 | name: path_provider_macos 198 | url: "https://pub.dartlang.org" 199 | source: hosted 200 | version: "2.0.5" 201 | path_provider_platform_interface: 202 | dependency: transitive 203 | description: 204 | name: path_provider_platform_interface 205 | url: "https://pub.dartlang.org" 206 | source: hosted 207 | version: "2.0.3" 208 | path_provider_windows: 209 | dependency: transitive 210 | description: 211 | name: path_provider_windows 212 | url: "https://pub.dartlang.org" 213 | source: hosted 214 | version: "2.0.5" 215 | platform: 216 | dependency: transitive 217 | description: 218 | name: platform 219 | url: "https://pub.dartlang.org" 220 | source: hosted 221 | version: "3.1.0" 222 | plugin_platform_interface: 223 | dependency: transitive 224 | description: 225 | name: plugin_platform_interface 226 | url: "https://pub.dartlang.org" 227 | source: hosted 228 | version: "2.1.2" 229 | process: 230 | dependency: transitive 231 | description: 232 | name: process 233 | url: "https://pub.dartlang.org" 234 | source: hosted 235 | version: "4.2.4" 236 | provider: 237 | dependency: transitive 238 | description: 239 | name: provider 240 | url: "https://pub.dartlang.org" 241 | source: hosted 242 | version: "6.0.2" 243 | rows_lint: 244 | dependency: "direct dev" 245 | description: 246 | name: rows_lint 247 | url: "https://pub.dartlang.org" 248 | source: hosted 249 | version: "0.1.0" 250 | scrollable_positioned_list: 251 | dependency: transitive 252 | description: 253 | name: scrollable_positioned_list 254 | url: "https://pub.dartlang.org" 255 | source: hosted 256 | version: "0.2.3" 257 | sky_engine: 258 | dependency: transitive 259 | description: flutter 260 | source: sdk 261 | version: "0.0.99" 262 | source_span: 263 | dependency: transitive 264 | description: 265 | name: source_span 266 | url: "https://pub.dartlang.org" 267 | source: hosted 268 | version: "1.8.1" 269 | stack_trace: 270 | dependency: transitive 271 | description: 272 | name: stack_trace 273 | url: "https://pub.dartlang.org" 274 | source: hosted 275 | version: "1.10.0" 276 | stream_channel: 277 | dependency: transitive 278 | description: 279 | name: stream_channel 280 | url: "https://pub.dartlang.org" 281 | source: hosted 282 | version: "2.1.0" 283 | string_scanner: 284 | dependency: transitive 285 | description: 286 | name: string_scanner 287 | url: "https://pub.dartlang.org" 288 | source: hosted 289 | version: "1.1.0" 290 | term_glyph: 291 | dependency: transitive 292 | description: 293 | name: term_glyph 294 | url: "https://pub.dartlang.org" 295 | source: hosted 296 | version: "1.2.0" 297 | test_api: 298 | dependency: transitive 299 | description: 300 | name: test_api 301 | url: "https://pub.dartlang.org" 302 | source: hosted 303 | version: "0.4.8" 304 | typed_data: 305 | dependency: transitive 306 | description: 307 | name: typed_data 308 | url: "https://pub.dartlang.org" 309 | source: hosted 310 | version: "1.3.0" 311 | url_launcher: 312 | dependency: "direct main" 313 | description: 314 | name: url_launcher 315 | url: "https://pub.dartlang.org" 316 | source: hosted 317 | version: "6.0.20" 318 | url_launcher_android: 319 | dependency: transitive 320 | description: 321 | name: url_launcher_android 322 | url: "https://pub.dartlang.org" 323 | source: hosted 324 | version: "6.0.15" 325 | url_launcher_ios: 326 | dependency: transitive 327 | description: 328 | name: url_launcher_ios 329 | url: "https://pub.dartlang.org" 330 | source: hosted 331 | version: "6.0.15" 332 | url_launcher_linux: 333 | dependency: transitive 334 | description: 335 | name: url_launcher_linux 336 | url: "https://pub.dartlang.org" 337 | source: hosted 338 | version: "3.0.0" 339 | url_launcher_macos: 340 | dependency: transitive 341 | description: 342 | name: url_launcher_macos 343 | url: "https://pub.dartlang.org" 344 | source: hosted 345 | version: "3.0.0" 346 | url_launcher_platform_interface: 347 | dependency: transitive 348 | description: 349 | name: url_launcher_platform_interface 350 | url: "https://pub.dartlang.org" 351 | source: hosted 352 | version: "2.0.5" 353 | url_launcher_web: 354 | dependency: transitive 355 | description: 356 | name: url_launcher_web 357 | url: "https://pub.dartlang.org" 358 | source: hosted 359 | version: "2.0.9" 360 | url_launcher_windows: 361 | dependency: transitive 362 | description: 363 | name: url_launcher_windows 364 | url: "https://pub.dartlang.org" 365 | source: hosted 366 | version: "3.0.0" 367 | vector_math: 368 | dependency: transitive 369 | description: 370 | name: vector_math 371 | url: "https://pub.dartlang.org" 372 | source: hosted 373 | version: "2.1.1" 374 | win32: 375 | dependency: transitive 376 | description: 377 | name: win32 378 | url: "https://pub.dartlang.org" 379 | source: hosted 380 | version: "2.5.0" 381 | xdg_directories: 382 | dependency: transitive 383 | description: 384 | name: xdg_directories 385 | url: "https://pub.dartlang.org" 386 | source: hosted 387 | version: "0.2.0+1" 388 | sdks: 389 | dart: ">=2.15.0 <3.0.0" 390 | flutter: ">=2.10.0" 391 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: data_explorer_example 2 | description: A new Flutter project. 3 | publish_to: 'none' 4 | version: 1.0.0+1 5 | 6 | environment: 7 | sdk: ">=2.12.0 <3.0.0" 8 | flutter: ">=1.17.0" 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | json_data_explorer: 13 | path: ../ 14 | 15 | http: ^0.13.4 16 | google_fonts: ^2.3.1 17 | url_launcher: ^6.0.20 18 | 19 | dev_dependencies: 20 | flutter_test: 21 | sdk: flutter 22 | rows_lint: 0.1.0 23 | 24 | flutter: 25 | uses-material-design: true 26 | -------------------------------------------------------------------------------- /example/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/example/web/favicon.png -------------------------------------------------------------------------------- /example/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/example/web/icons/Icon-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/example/web/icons/Icon-512.png -------------------------------------------------------------------------------- /example/web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/example/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/example/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /example/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | data_explorer_example 33 | 34 | 35 | 36 | 39 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /example/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "data_explorer_example", 3 | "short_name": "data_explorer_example", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /example/windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /example/windows/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Project-level configuration. 2 | cmake_minimum_required(VERSION 3.14) 3 | project(data_explorer_example LANGUAGES CXX) 4 | 5 | # The name of the executable created for the application. Change this to change 6 | # the on-disk name of your application. 7 | set(BINARY_NAME "data_explorer_example") 8 | 9 | # Explicitly opt in to modern CMake behaviors to avoid warnings with recent 10 | # versions of CMake. 11 | cmake_policy(SET CMP0063 NEW) 12 | 13 | # Define build configuration option. 14 | get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 15 | if(IS_MULTICONFIG) 16 | set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" 17 | CACHE STRING "" FORCE) 18 | else() 19 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 20 | set(CMAKE_BUILD_TYPE "Debug" CACHE 21 | STRING "Flutter build mode" FORCE) 22 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 23 | "Debug" "Profile" "Release") 24 | endif() 25 | endif() 26 | # Define settings for the Profile build mode. 27 | set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") 28 | set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") 29 | set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") 30 | set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") 31 | 32 | # Use Unicode for all projects. 33 | add_definitions(-DUNICODE -D_UNICODE) 34 | 35 | # Compilation settings that should be applied to most targets. 36 | # 37 | # Be cautious about adding new options here, as plugins use this function by 38 | # default. In most cases, you should add new options to specific targets instead 39 | # of modifying this function. 40 | function(APPLY_STANDARD_SETTINGS TARGET) 41 | target_compile_features(${TARGET} PUBLIC cxx_std_17) 42 | target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") 43 | target_compile_options(${TARGET} PRIVATE /EHsc) 44 | target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") 45 | target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") 46 | endfunction() 47 | 48 | # Flutter library and tool build rules. 49 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 50 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 51 | 52 | # Application build; see runner/CMakeLists.txt. 53 | add_subdirectory("runner") 54 | 55 | # Generated plugin build rules, which manage building the plugins and adding 56 | # them to the application. 57 | include(flutter/generated_plugins.cmake) 58 | 59 | 60 | # === Installation === 61 | # Support files are copied into place next to the executable, so that it can 62 | # run in place. This is done instead of making a separate bundle (as on Linux) 63 | # so that building and running from within Visual Studio will work. 64 | set(BUILD_BUNDLE_DIR "$") 65 | # Make the "install" step default, as it's required to run. 66 | set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) 67 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 68 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 69 | endif() 70 | 71 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 72 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") 73 | 74 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 75 | COMPONENT Runtime) 76 | 77 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 78 | COMPONENT Runtime) 79 | 80 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 81 | COMPONENT Runtime) 82 | 83 | if(PLUGIN_BUNDLED_LIBRARIES) 84 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" 85 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 86 | COMPONENT Runtime) 87 | endif() 88 | 89 | # Fully re-copy the assets directory on each build to avoid having stale files 90 | # from a previous install. 91 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 92 | install(CODE " 93 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 94 | " COMPONENT Runtime) 95 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 96 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 97 | 98 | # Install the AOT library on non-Debug builds only. 99 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 100 | CONFIGURATIONS Profile;Release 101 | COMPONENT Runtime) 102 | -------------------------------------------------------------------------------- /example/windows/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file controls Flutter-level build steps. It should not be edited. 2 | cmake_minimum_required(VERSION 3.14) 3 | 4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 5 | 6 | # Configuration provided via flutter tool. 7 | include(${EPHEMERAL_DIR}/generated_config.cmake) 8 | 9 | # TODO: Move the rest of this into files in ephemeral. See 10 | # https://github.com/flutter/flutter/issues/57146. 11 | set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") 12 | 13 | # === Flutter Library === 14 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") 15 | 16 | # Published to parent scope for install step. 17 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 18 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 19 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 20 | set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) 21 | 22 | list(APPEND FLUTTER_LIBRARY_HEADERS 23 | "flutter_export.h" 24 | "flutter_windows.h" 25 | "flutter_messenger.h" 26 | "flutter_plugin_registrar.h" 27 | "flutter_texture_registrar.h" 28 | ) 29 | list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") 30 | add_library(flutter INTERFACE) 31 | target_include_directories(flutter INTERFACE 32 | "${EPHEMERAL_DIR}" 33 | ) 34 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") 35 | add_dependencies(flutter flutter_assemble) 36 | 37 | # === Wrapper === 38 | list(APPEND CPP_WRAPPER_SOURCES_CORE 39 | "core_implementations.cc" 40 | "standard_codec.cc" 41 | ) 42 | list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") 43 | list(APPEND CPP_WRAPPER_SOURCES_PLUGIN 44 | "plugin_registrar.cc" 45 | ) 46 | list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") 47 | list(APPEND CPP_WRAPPER_SOURCES_APP 48 | "flutter_engine.cc" 49 | "flutter_view_controller.cc" 50 | ) 51 | list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") 52 | 53 | # Wrapper sources needed for a plugin. 54 | add_library(flutter_wrapper_plugin STATIC 55 | ${CPP_WRAPPER_SOURCES_CORE} 56 | ${CPP_WRAPPER_SOURCES_PLUGIN} 57 | ) 58 | apply_standard_settings(flutter_wrapper_plugin) 59 | set_target_properties(flutter_wrapper_plugin PROPERTIES 60 | POSITION_INDEPENDENT_CODE ON) 61 | set_target_properties(flutter_wrapper_plugin PROPERTIES 62 | CXX_VISIBILITY_PRESET hidden) 63 | target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) 64 | target_include_directories(flutter_wrapper_plugin PUBLIC 65 | "${WRAPPER_ROOT}/include" 66 | ) 67 | add_dependencies(flutter_wrapper_plugin flutter_assemble) 68 | 69 | # Wrapper sources needed for the runner. 70 | add_library(flutter_wrapper_app STATIC 71 | ${CPP_WRAPPER_SOURCES_CORE} 72 | ${CPP_WRAPPER_SOURCES_APP} 73 | ) 74 | apply_standard_settings(flutter_wrapper_app) 75 | target_link_libraries(flutter_wrapper_app PUBLIC flutter) 76 | target_include_directories(flutter_wrapper_app PUBLIC 77 | "${WRAPPER_ROOT}/include" 78 | ) 79 | add_dependencies(flutter_wrapper_app flutter_assemble) 80 | 81 | # === Flutter tool backend === 82 | # _phony_ is a non-existent file to force this command to run every time, 83 | # since currently there's no way to get a full input/output list from the 84 | # flutter tool. 85 | set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") 86 | set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) 87 | add_custom_command( 88 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 89 | ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} 90 | ${CPP_WRAPPER_SOURCES_APP} 91 | ${PHONY_OUTPUT} 92 | COMMAND ${CMAKE_COMMAND} -E env 93 | ${FLUTTER_TOOL_ENVIRONMENT} 94 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" 95 | windows-x64 $ 96 | VERBATIM 97 | ) 98 | add_custom_target(flutter_assemble DEPENDS 99 | "${FLUTTER_LIBRARY}" 100 | ${FLUTTER_LIBRARY_HEADERS} 101 | ${CPP_WRAPPER_SOURCES_CORE} 102 | ${CPP_WRAPPER_SOURCES_PLUGIN} 103 | ${CPP_WRAPPER_SOURCES_APP} 104 | ) 105 | -------------------------------------------------------------------------------- /example/windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | 11 | void RegisterPlugins(flutter::PluginRegistry* registry) { 12 | UrlLauncherWindowsRegisterWithRegistrar( 13 | registry->GetRegistrarForPlugin("UrlLauncherWindows")); 14 | } 15 | -------------------------------------------------------------------------------- /example/windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /example/windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | url_launcher_windows 7 | ) 8 | 9 | set(PLUGIN_BUNDLED_LIBRARIES) 10 | 11 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 12 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 13 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 14 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 15 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 16 | endforeach(plugin) 17 | -------------------------------------------------------------------------------- /example/windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(runner LANGUAGES CXX) 3 | 4 | # Define the application target. To change its name, change BINARY_NAME in the 5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer 6 | # work. 7 | # 8 | # Any new source files that you add to the application should be added here. 9 | add_executable(${BINARY_NAME} WIN32 10 | "flutter_window.cpp" 11 | "main.cpp" 12 | "utils.cpp" 13 | "win32_window.cpp" 14 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 15 | "Runner.rc" 16 | "runner.exe.manifest" 17 | ) 18 | 19 | # Apply the standard set of build settings. This can be removed for applications 20 | # that need different build settings. 21 | apply_standard_settings(${BINARY_NAME}) 22 | 23 | # Disable Windows macros that collide with C++ standard library functions. 24 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 25 | 26 | # Add dependency libraries and include directories. Add any application-specific 27 | # dependencies here. 28 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 29 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 30 | 31 | # Run the Flutter tool portions of the build. This must not be removed. 32 | add_dependencies(${BINARY_NAME} flutter_assemble) 33 | -------------------------------------------------------------------------------- /example/windows/runner/Runner.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #pragma code_page(65001) 4 | #include "resource.h" 5 | 6 | #define APSTUDIO_READONLY_SYMBOLS 7 | ///////////////////////////////////////////////////////////////////////////// 8 | // 9 | // Generated from the TEXTINCLUDE 2 resource. 10 | // 11 | #include "winres.h" 12 | 13 | ///////////////////////////////////////////////////////////////////////////// 14 | #undef APSTUDIO_READONLY_SYMBOLS 15 | 16 | ///////////////////////////////////////////////////////////////////////////// 17 | // English (United States) resources 18 | 19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 21 | 22 | #ifdef APSTUDIO_INVOKED 23 | ///////////////////////////////////////////////////////////////////////////// 24 | // 25 | // TEXTINCLUDE 26 | // 27 | 28 | 1 TEXTINCLUDE 29 | BEGIN 30 | "resource.h\0" 31 | END 32 | 33 | 2 TEXTINCLUDE 34 | BEGIN 35 | "#include ""winres.h""\r\n" 36 | "\0" 37 | END 38 | 39 | 3 TEXTINCLUDE 40 | BEGIN 41 | "\r\n" 42 | "\0" 43 | END 44 | 45 | #endif // APSTUDIO_INVOKED 46 | 47 | 48 | ///////////////////////////////////////////////////////////////////////////// 49 | // 50 | // Icon 51 | // 52 | 53 | // Icon with lowest ID value placed first to ensure application icon 54 | // remains consistent on all systems. 55 | IDI_APP_ICON ICON "resources\\app_icon.ico" 56 | 57 | 58 | ///////////////////////////////////////////////////////////////////////////// 59 | // 60 | // Version 61 | // 62 | 63 | #ifdef FLUTTER_BUILD_NUMBER 64 | #define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER 65 | #else 66 | #define VERSION_AS_NUMBER 1,0,0 67 | #endif 68 | 69 | #ifdef FLUTTER_BUILD_NAME 70 | #define VERSION_AS_STRING #FLUTTER_BUILD_NAME 71 | #else 72 | #define VERSION_AS_STRING "1.0.0" 73 | #endif 74 | 75 | VS_VERSION_INFO VERSIONINFO 76 | FILEVERSION VERSION_AS_NUMBER 77 | PRODUCTVERSION VERSION_AS_NUMBER 78 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK 79 | #ifdef _DEBUG 80 | FILEFLAGS VS_FF_DEBUG 81 | #else 82 | FILEFLAGS 0x0L 83 | #endif 84 | FILEOS VOS__WINDOWS32 85 | FILETYPE VFT_APP 86 | FILESUBTYPE 0x0L 87 | BEGIN 88 | BLOCK "StringFileInfo" 89 | BEGIN 90 | BLOCK "040904e4" 91 | BEGIN 92 | VALUE "CompanyName", "com.example" "\0" 93 | VALUE "FileDescription", "data_explorer_example" "\0" 94 | VALUE "FileVersion", VERSION_AS_STRING "\0" 95 | VALUE "InternalName", "data_explorer_example" "\0" 96 | VALUE "LegalCopyright", "Copyright (C) 2022 com.example. All rights reserved." "\0" 97 | VALUE "OriginalFilename", "data_explorer_example.exe" "\0" 98 | VALUE "ProductName", "data_explorer_example" "\0" 99 | VALUE "ProductVersion", VERSION_AS_STRING "\0" 100 | END 101 | END 102 | BLOCK "VarFileInfo" 103 | BEGIN 104 | VALUE "Translation", 0x409, 1252 105 | END 106 | END 107 | 108 | #endif // English (United States) resources 109 | ///////////////////////////////////////////////////////////////////////////// 110 | 111 | 112 | 113 | #ifndef APSTUDIO_INVOKED 114 | ///////////////////////////////////////////////////////////////////////////// 115 | // 116 | // Generated from the TEXTINCLUDE 3 resource. 117 | // 118 | 119 | 120 | ///////////////////////////////////////////////////////////////////////////// 121 | #endif // not APSTUDIO_INVOKED 122 | -------------------------------------------------------------------------------- /example/windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(const flutter::DartProject& project) 8 | : project_(project) {} 9 | 10 | FlutterWindow::~FlutterWindow() {} 11 | 12 | bool FlutterWindow::OnCreate() { 13 | if (!Win32Window::OnCreate()) { 14 | return false; 15 | } 16 | 17 | RECT frame = GetClientArea(); 18 | 19 | // The size here must match the window dimensions to avoid unnecessary surface 20 | // creation / destruction in the startup path. 21 | flutter_controller_ = std::make_unique( 22 | frame.right - frame.left, frame.bottom - frame.top, project_); 23 | // Ensure that basic setup of the controller was successful. 24 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 25 | return false; 26 | } 27 | RegisterPlugins(flutter_controller_->engine()); 28 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 29 | return true; 30 | } 31 | 32 | void FlutterWindow::OnDestroy() { 33 | if (flutter_controller_) { 34 | flutter_controller_ = nullptr; 35 | } 36 | 37 | Win32Window::OnDestroy(); 38 | } 39 | 40 | LRESULT 41 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 42 | WPARAM const wparam, 43 | LPARAM const lparam) noexcept { 44 | // Give Flutter, including plugins, an opportunity to handle window messages. 45 | if (flutter_controller_) { 46 | std::optional result = 47 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 48 | lparam); 49 | if (result) { 50 | return *result; 51 | } 52 | } 53 | 54 | switch (message) { 55 | case WM_FONTCHANGE: 56 | flutter_controller_->engine()->ReloadSystemFonts(); 57 | break; 58 | } 59 | 60 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 61 | } 62 | -------------------------------------------------------------------------------- /example/windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "win32_window.h" 10 | 11 | // A window that does nothing but host a Flutter view. 12 | class FlutterWindow : public Win32Window { 13 | public: 14 | // Creates a new FlutterWindow hosting a Flutter view running |project|. 15 | explicit FlutterWindow(const flutter::DartProject& project); 16 | virtual ~FlutterWindow(); 17 | 18 | protected: 19 | // Win32Window: 20 | bool OnCreate() override; 21 | void OnDestroy() override; 22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 23 | LPARAM const lparam) noexcept override; 24 | 25 | private: 26 | // The project to run. 27 | flutter::DartProject project_; 28 | 29 | // The Flutter instance hosted by this window. 30 | std::unique_ptr flutter_controller_; 31 | }; 32 | 33 | #endif // RUNNER_FLUTTER_WINDOW_H_ 34 | -------------------------------------------------------------------------------- /example/windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "utils.h" 7 | 8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 9 | _In_ wchar_t *command_line, _In_ int show_command) { 10 | // Attach to console when present (e.g., 'flutter run') or create a 11 | // new console when running with a debugger. 12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 13 | CreateAndAttachConsole(); 14 | } 15 | 16 | // Initialize COM, so that it is available for use in the library and/or 17 | // plugins. 18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 19 | 20 | flutter::DartProject project(L"data"); 21 | 22 | std::vector command_line_arguments = 23 | GetCommandLineArguments(); 24 | 25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 26 | 27 | FlutterWindow window(project); 28 | Win32Window::Point origin(10, 10); 29 | Win32Window::Size size(1280, 720); 30 | if (!window.CreateAndShow(L"data_explorer_example", origin, size)) { 31 | return EXIT_FAILURE; 32 | } 33 | window.SetQuitOnClose(true); 34 | 35 | ::MSG msg; 36 | while (::GetMessage(&msg, nullptr, 0, 0)) { 37 | ::TranslateMessage(&msg); 38 | ::DispatchMessage(&msg); 39 | } 40 | 41 | ::CoUninitialize(); 42 | return EXIT_SUCCESS; 43 | } 44 | -------------------------------------------------------------------------------- /example/windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /example/windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/example/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /example/windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /example/windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr); 51 | if (target_length == 0) { 52 | return std::string(); 53 | } 54 | std::string utf8_string; 55 | utf8_string.resize(target_length); 56 | int converted_length = ::WideCharToMultiByte( 57 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 58 | -1, utf8_string.data(), 59 | target_length, nullptr, nullptr); 60 | if (converted_length == 0) { 61 | return std::string(); 62 | } 63 | return utf8_string; 64 | } 65 | -------------------------------------------------------------------------------- /example/windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | -------------------------------------------------------------------------------- /example/windows/runner/win32_window.cpp: -------------------------------------------------------------------------------- 1 | #include "win32_window.h" 2 | 3 | #include 4 | 5 | #include "resource.h" 6 | 7 | namespace { 8 | 9 | constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; 10 | 11 | // The number of Win32Window objects that currently exist. 12 | static int g_active_window_count = 0; 13 | 14 | using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); 15 | 16 | // Scale helper to convert logical scaler values to physical using passed in 17 | // scale factor 18 | int Scale(int source, double scale_factor) { 19 | return static_cast(source * scale_factor); 20 | } 21 | 22 | // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. 23 | // This API is only needed for PerMonitor V1 awareness mode. 24 | void EnableFullDpiSupportIfAvailable(HWND hwnd) { 25 | HMODULE user32_module = LoadLibraryA("User32.dll"); 26 | if (!user32_module) { 27 | return; 28 | } 29 | auto enable_non_client_dpi_scaling = 30 | reinterpret_cast( 31 | GetProcAddress(user32_module, "EnableNonClientDpiScaling")); 32 | if (enable_non_client_dpi_scaling != nullptr) { 33 | enable_non_client_dpi_scaling(hwnd); 34 | FreeLibrary(user32_module); 35 | } 36 | } 37 | 38 | } // namespace 39 | 40 | // Manages the Win32Window's window class registration. 41 | class WindowClassRegistrar { 42 | public: 43 | ~WindowClassRegistrar() = default; 44 | 45 | // Returns the singleton registar instance. 46 | static WindowClassRegistrar* GetInstance() { 47 | if (!instance_) { 48 | instance_ = new WindowClassRegistrar(); 49 | } 50 | return instance_; 51 | } 52 | 53 | // Returns the name of the window class, registering the class if it hasn't 54 | // previously been registered. 55 | const wchar_t* GetWindowClass(); 56 | 57 | // Unregisters the window class. Should only be called if there are no 58 | // instances of the window. 59 | void UnregisterWindowClass(); 60 | 61 | private: 62 | WindowClassRegistrar() = default; 63 | 64 | static WindowClassRegistrar* instance_; 65 | 66 | bool class_registered_ = false; 67 | }; 68 | 69 | WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; 70 | 71 | const wchar_t* WindowClassRegistrar::GetWindowClass() { 72 | if (!class_registered_) { 73 | WNDCLASS window_class{}; 74 | window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); 75 | window_class.lpszClassName = kWindowClassName; 76 | window_class.style = CS_HREDRAW | CS_VREDRAW; 77 | window_class.cbClsExtra = 0; 78 | window_class.cbWndExtra = 0; 79 | window_class.hInstance = GetModuleHandle(nullptr); 80 | window_class.hIcon = 81 | LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); 82 | window_class.hbrBackground = 0; 83 | window_class.lpszMenuName = nullptr; 84 | window_class.lpfnWndProc = Win32Window::WndProc; 85 | RegisterClass(&window_class); 86 | class_registered_ = true; 87 | } 88 | return kWindowClassName; 89 | } 90 | 91 | void WindowClassRegistrar::UnregisterWindowClass() { 92 | UnregisterClass(kWindowClassName, nullptr); 93 | class_registered_ = false; 94 | } 95 | 96 | Win32Window::Win32Window() { 97 | ++g_active_window_count; 98 | } 99 | 100 | Win32Window::~Win32Window() { 101 | --g_active_window_count; 102 | Destroy(); 103 | } 104 | 105 | bool Win32Window::CreateAndShow(const std::wstring& title, 106 | const Point& origin, 107 | const Size& size) { 108 | Destroy(); 109 | 110 | const wchar_t* window_class = 111 | WindowClassRegistrar::GetInstance()->GetWindowClass(); 112 | 113 | const POINT target_point = {static_cast(origin.x), 114 | static_cast(origin.y)}; 115 | HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); 116 | UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); 117 | double scale_factor = dpi / 96.0; 118 | 119 | HWND window = CreateWindow( 120 | window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, 121 | Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), 122 | Scale(size.width, scale_factor), Scale(size.height, scale_factor), 123 | nullptr, nullptr, GetModuleHandle(nullptr), this); 124 | 125 | if (!window) { 126 | return false; 127 | } 128 | 129 | return OnCreate(); 130 | } 131 | 132 | // static 133 | LRESULT CALLBACK Win32Window::WndProc(HWND const window, 134 | UINT const message, 135 | WPARAM const wparam, 136 | LPARAM const lparam) noexcept { 137 | if (message == WM_NCCREATE) { 138 | auto window_struct = reinterpret_cast(lparam); 139 | SetWindowLongPtr(window, GWLP_USERDATA, 140 | reinterpret_cast(window_struct->lpCreateParams)); 141 | 142 | auto that = static_cast(window_struct->lpCreateParams); 143 | EnableFullDpiSupportIfAvailable(window); 144 | that->window_handle_ = window; 145 | } else if (Win32Window* that = GetThisFromHandle(window)) { 146 | return that->MessageHandler(window, message, wparam, lparam); 147 | } 148 | 149 | return DefWindowProc(window, message, wparam, lparam); 150 | } 151 | 152 | LRESULT 153 | Win32Window::MessageHandler(HWND hwnd, 154 | UINT const message, 155 | WPARAM const wparam, 156 | LPARAM const lparam) noexcept { 157 | switch (message) { 158 | case WM_DESTROY: 159 | window_handle_ = nullptr; 160 | Destroy(); 161 | if (quit_on_close_) { 162 | PostQuitMessage(0); 163 | } 164 | return 0; 165 | 166 | case WM_DPICHANGED: { 167 | auto newRectSize = reinterpret_cast(lparam); 168 | LONG newWidth = newRectSize->right - newRectSize->left; 169 | LONG newHeight = newRectSize->bottom - newRectSize->top; 170 | 171 | SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, 172 | newHeight, SWP_NOZORDER | SWP_NOACTIVATE); 173 | 174 | return 0; 175 | } 176 | case WM_SIZE: { 177 | RECT rect = GetClientArea(); 178 | if (child_content_ != nullptr) { 179 | // Size and position the child window. 180 | MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, 181 | rect.bottom - rect.top, TRUE); 182 | } 183 | return 0; 184 | } 185 | 186 | case WM_ACTIVATE: 187 | if (child_content_ != nullptr) { 188 | SetFocus(child_content_); 189 | } 190 | return 0; 191 | } 192 | 193 | return DefWindowProc(window_handle_, message, wparam, lparam); 194 | } 195 | 196 | void Win32Window::Destroy() { 197 | OnDestroy(); 198 | 199 | if (window_handle_) { 200 | DestroyWindow(window_handle_); 201 | window_handle_ = nullptr; 202 | } 203 | if (g_active_window_count == 0) { 204 | WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); 205 | } 206 | } 207 | 208 | Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { 209 | return reinterpret_cast( 210 | GetWindowLongPtr(window, GWLP_USERDATA)); 211 | } 212 | 213 | void Win32Window::SetChildContent(HWND content) { 214 | child_content_ = content; 215 | SetParent(content, window_handle_); 216 | RECT frame = GetClientArea(); 217 | 218 | MoveWindow(content, frame.left, frame.top, frame.right - frame.left, 219 | frame.bottom - frame.top, true); 220 | 221 | SetFocus(child_content_); 222 | } 223 | 224 | RECT Win32Window::GetClientArea() { 225 | RECT frame; 226 | GetClientRect(window_handle_, &frame); 227 | return frame; 228 | } 229 | 230 | HWND Win32Window::GetHandle() { 231 | return window_handle_; 232 | } 233 | 234 | void Win32Window::SetQuitOnClose(bool quit_on_close) { 235 | quit_on_close_ = quit_on_close; 236 | } 237 | 238 | bool Win32Window::OnCreate() { 239 | // No-op; provided for subclasses. 240 | return true; 241 | } 242 | 243 | void Win32Window::OnDestroy() { 244 | // No-op; provided for subclasses. 245 | } 246 | -------------------------------------------------------------------------------- /example/windows/runner/win32_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_WIN32_WINDOW_H_ 2 | #define RUNNER_WIN32_WINDOW_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be 11 | // inherited from by classes that wish to specialize with custom 12 | // rendering and input handling 13 | class Win32Window { 14 | public: 15 | struct Point { 16 | unsigned int x; 17 | unsigned int y; 18 | Point(unsigned int x, unsigned int y) : x(x), y(y) {} 19 | }; 20 | 21 | struct Size { 22 | unsigned int width; 23 | unsigned int height; 24 | Size(unsigned int width, unsigned int height) 25 | : width(width), height(height) {} 26 | }; 27 | 28 | Win32Window(); 29 | virtual ~Win32Window(); 30 | 31 | // Creates and shows a win32 window with |title| and position and size using 32 | // |origin| and |size|. New windows are created on the default monitor. Window 33 | // sizes are specified to the OS in physical pixels, hence to ensure a 34 | // consistent size to will treat the width height passed in to this function 35 | // as logical pixels and scale to appropriate for the default monitor. Returns 36 | // true if the window was created successfully. 37 | bool CreateAndShow(const std::wstring& title, 38 | const Point& origin, 39 | const Size& size); 40 | 41 | // Release OS resources associated with window. 42 | void Destroy(); 43 | 44 | // Inserts |content| into the window tree. 45 | void SetChildContent(HWND content); 46 | 47 | // Returns the backing Window handle to enable clients to set icon and other 48 | // window properties. Returns nullptr if the window has been destroyed. 49 | HWND GetHandle(); 50 | 51 | // If true, closing this window will quit the application. 52 | void SetQuitOnClose(bool quit_on_close); 53 | 54 | // Return a RECT representing the bounds of the current client area. 55 | RECT GetClientArea(); 56 | 57 | protected: 58 | // Processes and route salient window messages for mouse handling, 59 | // size change and DPI. Delegates handling of these to member overloads that 60 | // inheriting classes can handle. 61 | virtual LRESULT MessageHandler(HWND window, 62 | UINT const message, 63 | WPARAM const wparam, 64 | LPARAM const lparam) noexcept; 65 | 66 | // Called when CreateAndShow is called, allowing subclass window-related 67 | // setup. Subclasses should return false if setup fails. 68 | virtual bool OnCreate(); 69 | 70 | // Called when Destroy is called. 71 | virtual void OnDestroy(); 72 | 73 | private: 74 | friend class WindowClassRegistrar; 75 | 76 | // OS callback called by message pump. Handles the WM_NCCREATE message which 77 | // is passed when the non-client area is being created and enables automatic 78 | // non-client DPI scaling so that the non-client area automatically 79 | // responsponds to changes in DPI. All other messages are handled by 80 | // MessageHandler. 81 | static LRESULT CALLBACK WndProc(HWND const window, 82 | UINT const message, 83 | WPARAM const wparam, 84 | LPARAM const lparam) noexcept; 85 | 86 | // Retrieves a class instance pointer for |window| 87 | static Win32Window* GetThisFromHandle(HWND const window) noexcept; 88 | 89 | bool quit_on_close_ = false; 90 | 91 | // window handle for top level window. 92 | HWND window_handle_ = nullptr; 93 | 94 | // window handle for hosted content. 95 | HWND child_content_ = nullptr; 96 | }; 97 | 98 | #endif // RUNNER_WIN32_WINDOW_H_ 99 | -------------------------------------------------------------------------------- /lib/json_data_explorer.dart: -------------------------------------------------------------------------------- 1 | library data_explorer; 2 | 3 | export 'src/data_explorer_store.dart'; 4 | export 'src/data_explorer_theme.dart'; 5 | export 'src/json_data_explorer.dart'; 6 | -------------------------------------------------------------------------------- /lib/src/data_explorer_theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// Theme used to display the [JsonDataExplorer]. 4 | @immutable 5 | class DataExplorerTheme { 6 | /// Text style used to display json class/arrays key attributes. 7 | /// 8 | /// Defaults to [propertyKeyTextStyle] if not set. 9 | final TextStyle rootKeyTextStyle; 10 | 11 | /// Text style used to display json property key attributes. 12 | final TextStyle propertyKeyTextStyle; 13 | 14 | /// Text style to display the values of of json attributes. 15 | final TextStyle valueTextStyle; 16 | 17 | /// Text style use to highlight search result matches on json attribute keys. 18 | final TextStyle keySearchHighlightTextStyle; 19 | 20 | /// Text style use to highlight search result matches on json attribute 21 | /// values. 22 | final TextStyle valueSearchHighlightTextStyle; 23 | 24 | /// Text style used to highlight the current focused search result node key. 25 | /// 26 | /// If not set falls back to [keySearchHighlightTextStyle]. 27 | final TextStyle focusedKeySearchNodeHighlightTextStyle; 28 | 29 | /// Text style used to highlight the current focused search result node value. 30 | /// 31 | /// If not set falls back to [valueSearchHighlightTextStyle]. 32 | final TextStyle focusedValueSearchHighlightTextStyle; 33 | 34 | /// Indentation lines color. 35 | final Color indentationLineColor; 36 | 37 | /// Padding used to indent nodes. 38 | final double indentationPadding; 39 | 40 | /// An extra factor applied on [indentationPadding] used when rendering 41 | /// properties. 42 | final double propertyIndentationPaddingFactor; 43 | 44 | /// Cursor hover highlight color. 45 | /// 46 | /// null to disable the highlight. 47 | final Color? highlightColor; 48 | 49 | DataExplorerTheme({ 50 | TextStyle? rootKeyTextStyle, 51 | TextStyle? propertyKeyTextStyle, 52 | TextStyle? keySearchHighlightTextStyle, 53 | TextStyle? valueTextStyle, 54 | TextStyle? valueSearchHighlightTextStyle, 55 | TextStyle? focusedKeySearchHighlightTextStyle, 56 | TextStyle? focusedValueSearchHighlightTextStyle, 57 | this.indentationLineColor = Colors.grey, 58 | this.highlightColor, 59 | this.indentationPadding = 8.0, 60 | this.propertyIndentationPaddingFactor = 4, 61 | }) : rootKeyTextStyle = rootKeyTextStyle ?? 62 | (propertyKeyTextStyle ?? 63 | DataExplorerTheme.defaultTheme.rootKeyTextStyle), 64 | propertyKeyTextStyle = propertyKeyTextStyle ?? 65 | DataExplorerTheme.defaultTheme.propertyKeyTextStyle, 66 | keySearchHighlightTextStyle = keySearchHighlightTextStyle ?? 67 | DataExplorerTheme.defaultTheme.keySearchHighlightTextStyle, 68 | valueTextStyle = 69 | valueTextStyle ?? DataExplorerTheme.defaultTheme.valueTextStyle, 70 | valueSearchHighlightTextStyle = valueSearchHighlightTextStyle ?? 71 | DataExplorerTheme.defaultTheme.valueSearchHighlightTextStyle, 72 | focusedKeySearchNodeHighlightTextStyle = 73 | focusedKeySearchHighlightTextStyle ?? 74 | (keySearchHighlightTextStyle ?? 75 | DataExplorerTheme 76 | .defaultTheme.focusedKeySearchNodeHighlightTextStyle), 77 | focusedValueSearchHighlightTextStyle = 78 | focusedValueSearchHighlightTextStyle ?? 79 | (valueSearchHighlightTextStyle ?? 80 | DataExplorerTheme 81 | .defaultTheme.focusedValueSearchHighlightTextStyle); 82 | 83 | const DataExplorerTheme._({ 84 | required this.rootKeyTextStyle, 85 | required this.propertyKeyTextStyle, 86 | required this.keySearchHighlightTextStyle, 87 | required this.valueTextStyle, 88 | required this.valueSearchHighlightTextStyle, 89 | required this.focusedKeySearchNodeHighlightTextStyle, 90 | required this.focusedValueSearchHighlightTextStyle, 91 | required this.indentationLineColor, 92 | required this.highlightColor, 93 | required this.indentationPadding, 94 | required this.propertyIndentationPaddingFactor, 95 | }); 96 | 97 | /// Default theme used if no theme is set. 98 | static const defaultTheme = DataExplorerTheme._( 99 | rootKeyTextStyle: TextStyle( 100 | fontSize: 14, 101 | color: Colors.black, 102 | fontWeight: FontWeight.bold, 103 | ), 104 | propertyKeyTextStyle: TextStyle( 105 | fontSize: 14, 106 | color: Colors.black54, 107 | fontWeight: FontWeight.bold, 108 | ), 109 | valueTextStyle: TextStyle( 110 | fontSize: 14, 111 | color: Colors.redAccent, 112 | ), 113 | keySearchHighlightTextStyle: TextStyle( 114 | fontSize: 14, 115 | color: Colors.black, 116 | fontWeight: FontWeight.bold, 117 | backgroundColor: Colors.amberAccent, 118 | ), 119 | valueSearchHighlightTextStyle: TextStyle( 120 | fontSize: 14, 121 | color: Colors.redAccent, 122 | fontWeight: FontWeight.bold, 123 | backgroundColor: Colors.amberAccent, 124 | ), 125 | focusedKeySearchNodeHighlightTextStyle: TextStyle( 126 | fontSize: 14, 127 | color: Colors.black, 128 | fontWeight: FontWeight.bold, 129 | backgroundColor: Colors.lightGreen, 130 | ), 131 | focusedValueSearchHighlightTextStyle: TextStyle( 132 | fontSize: 14, 133 | color: Colors.redAccent, 134 | fontWeight: FontWeight.bold, 135 | backgroundColor: Colors.lightGreen, 136 | ), 137 | indentationLineColor: Colors.grey, 138 | highlightColor: Colors.black12, 139 | indentationPadding: 8.0, 140 | propertyIndentationPaddingFactor: 4, 141 | ); 142 | 143 | DataExplorerTheme copyWith({ 144 | TextStyle? rootKeyTextStyle, 145 | TextStyle? propertyKeyTextStyle, 146 | TextStyle? keySearchHighlightTextStyle, 147 | TextStyle? valueTextStyle, 148 | TextStyle? valueSearchHighlightTextStyle, 149 | TextStyle? focusedKeySearchNodeHighlightTextStyle, 150 | TextStyle? focusedValueSearchHighlightTextStyle, 151 | Color? indentationLineColor, 152 | Color? highlightColor, 153 | double? indentationPadding, 154 | double? propertyIndentationPaddingFactor, 155 | }) => 156 | DataExplorerTheme( 157 | rootKeyTextStyle: rootKeyTextStyle ?? this.rootKeyTextStyle, 158 | propertyKeyTextStyle: propertyKeyTextStyle ?? this.propertyKeyTextStyle, 159 | keySearchHighlightTextStyle: 160 | keySearchHighlightTextStyle ?? this.keySearchHighlightTextStyle, 161 | valueTextStyle: valueTextStyle ?? this.valueTextStyle, 162 | valueSearchHighlightTextStyle: 163 | valueSearchHighlightTextStyle ?? this.valueSearchHighlightTextStyle, 164 | indentationLineColor: indentationLineColor ?? this.indentationLineColor, 165 | highlightColor: highlightColor ?? this.highlightColor, 166 | indentationPadding: indentationPadding ?? this.indentationPadding, 167 | propertyIndentationPaddingFactor: propertyIndentationPaddingFactor ?? 168 | this.propertyIndentationPaddingFactor, 169 | focusedKeySearchHighlightTextStyle: 170 | focusedKeySearchNodeHighlightTextStyle ?? 171 | this.focusedKeySearchNodeHighlightTextStyle, 172 | focusedValueSearchHighlightTextStyle: 173 | focusedValueSearchHighlightTextStyle ?? 174 | this.focusedValueSearchHighlightTextStyle, 175 | ); 176 | 177 | @override 178 | bool operator ==(Object other) { 179 | if (identical(this, other)) { 180 | return true; 181 | } 182 | if (other.runtimeType != runtimeType) { 183 | return false; 184 | } 185 | return other is DataExplorerTheme && 186 | rootKeyTextStyle == other.rootKeyTextStyle && 187 | propertyKeyTextStyle == other.propertyKeyTextStyle && 188 | valueTextStyle == other.valueTextStyle && 189 | indentationLineColor == other.indentationLineColor && 190 | highlightColor == other.highlightColor && 191 | indentationPadding == other.indentationPadding && 192 | propertyIndentationPaddingFactor == 193 | other.propertyIndentationPaddingFactor && 194 | keySearchHighlightTextStyle == other.keySearchHighlightTextStyle && 195 | valueSearchHighlightTextStyle == other.valueSearchHighlightTextStyle && 196 | focusedKeySearchNodeHighlightTextStyle == 197 | other.focusedKeySearchNodeHighlightTextStyle && 198 | focusedValueSearchHighlightTextStyle == 199 | other.focusedValueSearchHighlightTextStyle; 200 | } 201 | 202 | @override 203 | int get hashCode => Object.hash( 204 | rootKeyTextStyle, 205 | propertyKeyTextStyle, 206 | valueTextStyle, 207 | indentationLineColor, 208 | highlightColor, 209 | indentationPadding, 210 | propertyIndentationPaddingFactor, 211 | keySearchHighlightTextStyle, 212 | valueSearchHighlightTextStyle, 213 | focusedKeySearchNodeHighlightTextStyle, 214 | focusedValueSearchHighlightTextStyle, 215 | ); 216 | } 217 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: json_data_explorer 2 | description: A highly customizable widget to render and interact with JSON objects. 3 | version: 0.1.0 4 | repository: https://github.com/rows/json_data_explorer 5 | homepage: https://github.com/rows/json_data_explorer 6 | issue_tracker: https://github.com/rows/json_data_explorer/issues 7 | 8 | environment: 9 | sdk: ">=2.12.0 <3.0.0" 10 | flutter: ">=1.17.0" 11 | 12 | dependencies: 13 | flutter: 14 | sdk: flutter 15 | scrollable_positioned_list: ^0.2.3 16 | provider: ^6.0.2 17 | 18 | dev_dependencies: 19 | flutter_test: 20 | sdk: flutter 21 | rows_lint: 0.1.0 22 | mocktail: ^0.3.0 23 | golden_toolkit: ^0.13.0 24 | 25 | flutter: 26 | uses-material-design: true -------------------------------------------------------------------------------- /test/golden/flutter_test_config.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:golden_toolkit/golden_toolkit.dart'; 4 | 5 | Future testExecutable(FutureOr Function() testMain) async { 6 | await loadAppFonts(); 7 | return testMain(); 8 | } 9 | -------------------------------------------------------------------------------- /test/golden/goldens/data_explorer/customization.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/test/golden/goldens/data_explorer/customization.png -------------------------------------------------------------------------------- /test/golden/goldens/data_explorer/interaction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/test/golden/goldens/data_explorer/interaction.png -------------------------------------------------------------------------------- /test/golden/goldens/indentation/array_indentation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/test/golden/goldens/indentation/array_indentation.png -------------------------------------------------------------------------------- /test/golden/goldens/indentation/class_indentation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/test/golden/goldens/indentation/class_indentation.png -------------------------------------------------------------------------------- /test/golden/goldens/indentation/property_indentation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/test/golden/goldens/indentation/property_indentation.png -------------------------------------------------------------------------------- /test/golden/goldens/json_attribute.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/test/golden/goldens/json_attribute.png -------------------------------------------------------------------------------- /test/golden/goldens/multi_size_test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/test/golden/goldens/multi_size_test.png -------------------------------------------------------------------------------- /test/golden/goldens/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rows/json_data_explorer/94fc1515f472f20c9e120b6e01b04667fc8cf1c4/test/golden/goldens/search.png -------------------------------------------------------------------------------- /test/golden/json_attribute_test.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: avoid_private_typedef_functions 2 | import 'dart:convert'; 3 | 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_test/flutter_test.dart'; 6 | import 'package:golden_toolkit/golden_toolkit.dart'; 7 | import 'package:json_data_explorer/json_data_explorer.dart'; 8 | import 'package:provider/provider.dart'; 9 | 10 | import 'test_data.dart'; 11 | 12 | typedef _NodeBuilder = Widget Function(int treeDepth, DataExplorerTheme theme); 13 | 14 | void main() { 15 | testGoldens('Json attribute', (tester) async { 16 | final dynamic jsonObject = json.decode(nobelPrizesJson); 17 | 18 | final node = NodeViewModelState.fromProperty( 19 | treeDepth: 0, 20 | key: 'property', 21 | value: 'value', 22 | parent: null, 23 | ); 24 | 25 | final widget = ChangeNotifierProvider( 26 | create: (context) => DataExplorerStore()..buildNodes(jsonObject), 27 | child: Consumer( 28 | builder: (context, state, child) => JsonAttribute( 29 | node: node, 30 | theme: DataExplorerTheme.defaultTheme, 31 | ), 32 | ), 33 | ); 34 | 35 | final builder = GoldenBuilder.column(bgColor: Colors.white) 36 | ..addScenario('Default font size', widget) 37 | ..addTextScaleScenario('Large font size', widget, textScaleFactor: 2.0) 38 | ..addTextScaleScenario('Largest font', widget, textScaleFactor: 3.0); 39 | 40 | await tester.pumpWidgetBuilder(builder.build()); 41 | await screenMatchesGolden(tester, 'json_attribute'); 42 | }); 43 | 44 | group('Search', () { 45 | const _searchTestJson = ''' 46 | { 47 | "property": "property value", 48 | "anotherProperty": "another property value" 49 | } 50 | '''; 51 | 52 | testGoldens('Highlight', (tester) async { 53 | final dynamic jsonObject = json.decode(_searchTestJson); 54 | Widget buildWidget({ 55 | required String searchTerm, 56 | Function(DataExplorerStore store)? onStoreCreate, 57 | DataExplorerTheme? theme, 58 | }) { 59 | return ChangeNotifierProvider( 60 | create: (context) { 61 | final store = DataExplorerStore() 62 | ..buildNodes(jsonObject) 63 | ..search(searchTerm); 64 | onStoreCreate?.call(store); 65 | return store; 66 | }, 67 | child: Consumer( 68 | builder: (context, state, child) => JsonAttribute( 69 | node: state.displayNodes.last, 70 | theme: theme ?? DataExplorerTheme.defaultTheme, 71 | ), 72 | ), 73 | ); 74 | } 75 | 76 | final customTheme = DataExplorerTheme( 77 | keySearchHighlightTextStyle: const TextStyle( 78 | fontSize: 18, 79 | color: Colors.black, 80 | fontWeight: FontWeight.bold, 81 | backgroundColor: Colors.green, 82 | ), 83 | valueSearchHighlightTextStyle: const TextStyle( 84 | fontSize: 18, 85 | color: Colors.black, 86 | backgroundColor: Colors.purpleAccent, 87 | ), 88 | focusedKeySearchHighlightTextStyle: const TextStyle( 89 | fontSize: 18, 90 | color: Colors.white, 91 | fontWeight: FontWeight.bold, 92 | backgroundColor: Colors.black, 93 | ), 94 | focusedValueSearchHighlightTextStyle: const TextStyle( 95 | fontSize: 18, 96 | color: Colors.grey, 97 | fontWeight: FontWeight.bold, 98 | backgroundColor: Colors.red, 99 | ), 100 | ); 101 | 102 | final builder = GoldenBuilder.column(bgColor: Colors.white) 103 | ..addScenario( 104 | 'highlight', 105 | buildWidget( 106 | searchTerm: 'property', 107 | ), 108 | ) 109 | ..addScenario( 110 | 'property focused highlight', 111 | buildWidget( 112 | searchTerm: 'property', 113 | onStoreCreate: (store) => store 114 | ..focusNextSearchResult() 115 | ..focusNextSearchResult(), 116 | ), 117 | ) 118 | ..addScenario( 119 | 'value focused highlight', 120 | buildWidget( 121 | searchTerm: 'property', 122 | onStoreCreate: (store) => store 123 | ..focusNextSearchResult() 124 | ..focusNextSearchResult() 125 | ..focusNextSearchResult(), 126 | ), 127 | ) 128 | ..addScenario( 129 | 'custom theme highlight', 130 | buildWidget( 131 | searchTerm: 'property', 132 | theme: customTheme, 133 | ), 134 | ) 135 | ..addScenario( 136 | 'custom theme property focused highlight', 137 | buildWidget( 138 | searchTerm: 'property', 139 | theme: customTheme, 140 | onStoreCreate: (store) => store 141 | ..focusNextSearchResult() 142 | ..focusNextSearchResult(), 143 | ), 144 | ) 145 | ..addScenario( 146 | 'custom theme value focused highlight', 147 | buildWidget( 148 | searchTerm: 'property', 149 | theme: customTheme, 150 | onStoreCreate: (store) => store 151 | ..focusNextSearchResult() 152 | ..focusNextSearchResult() 153 | ..focusNextSearchResult(), 154 | ), 155 | ); 156 | 157 | await tester.pumpWidgetBuilder( 158 | builder.build(), 159 | surfaceSize: const Size(400, 600), 160 | ); 161 | await screenMatchesGolden(tester, 'search'); 162 | }); 163 | }); 164 | 165 | group('Indentation', () { 166 | Future testIndentationGuidelines( 167 | WidgetTester tester, { 168 | required _NodeBuilder nodeBuilder, 169 | required String goldenName, 170 | }) async { 171 | final builder = GoldenBuilder.column(bgColor: Colors.white) 172 | ..addScenario( 173 | 'no indentation', 174 | nodeBuilder(0, DataExplorerTheme.defaultTheme), 175 | ) 176 | ..addScenario( 177 | '1 step', 178 | nodeBuilder(1, DataExplorerTheme.defaultTheme), 179 | ) 180 | ..addScenario( 181 | '2 steps', 182 | nodeBuilder(2, DataExplorerTheme.defaultTheme), 183 | ) 184 | ..addScenario( 185 | '3 steps', 186 | nodeBuilder(3, DataExplorerTheme.defaultTheme), 187 | ) 188 | ..addScenario( 189 | '4 steps', 190 | nodeBuilder(4, DataExplorerTheme.defaultTheme), 191 | ) 192 | ..addScenario( 193 | 'custom color', 194 | nodeBuilder( 195 | 4, 196 | DataExplorerTheme( 197 | indentationLineColor: Colors.blue, 198 | ), 199 | ), 200 | ) 201 | ..addScenario( 202 | 'no guidelines', 203 | nodeBuilder( 204 | 4, 205 | DataExplorerTheme( 206 | indentationLineColor: Colors.transparent, 207 | ), 208 | ), 209 | ); 210 | 211 | await tester.pumpWidgetBuilder( 212 | builder.build(), 213 | wrapper: (widget) => materialAppWrapper()( 214 | ChangeNotifierProvider( 215 | create: (context) => DataExplorerStore(), 216 | child: Consumer( 217 | builder: (context, state, child) => widget, 218 | ), 219 | ), 220 | ), 221 | surfaceSize: const Size(200, 600), 222 | ); 223 | await screenMatchesGolden(tester, 'indentation/$goldenName'); 224 | } 225 | 226 | testGoldens('Property indentation guidelines', (tester) async { 227 | await testIndentationGuidelines( 228 | tester, 229 | goldenName: 'property_indentation', 230 | nodeBuilder: (treeDepth, theme) { 231 | final node = NodeViewModelState.fromProperty( 232 | treeDepth: treeDepth, 233 | key: 'property', 234 | value: 'value', 235 | parent: null, 236 | ); 237 | return JsonAttribute( 238 | node: node, 239 | theme: theme, 240 | ); 241 | }, 242 | ); 243 | }); 244 | 245 | testGoldens('Property indentation guidelines', (tester) async { 246 | await testIndentationGuidelines( 247 | tester, 248 | goldenName: 'class_indentation', 249 | nodeBuilder: (treeDepth, theme) { 250 | final node = NodeViewModelState.fromClass( 251 | treeDepth: treeDepth, 252 | key: 'class', 253 | parent: null, 254 | )..value = {}; 255 | return JsonAttribute( 256 | node: node, 257 | theme: theme, 258 | ); 259 | }, 260 | ); 261 | }); 262 | 263 | testGoldens('Array indentation guidelines', (tester) async { 264 | await testIndentationGuidelines( 265 | tester, 266 | goldenName: 'array_indentation', 267 | nodeBuilder: (treeDepth, theme) { 268 | final node = NodeViewModelState.fromArray( 269 | treeDepth: treeDepth, 270 | key: 'array', 271 | parent: null, 272 | )..value = []; 273 | return JsonAttribute( 274 | node: node, 275 | theme: theme, 276 | ); 277 | }, 278 | ); 279 | }); 280 | }); 281 | } 282 | -------------------------------------------------------------------------------- /test/golden/json_data_explorer_test.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: avoid_private_typedef_functions 2 | import 'dart:convert'; 3 | import 'dart:ui'; 4 | 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter_test/flutter_test.dart'; 7 | import 'package:golden_toolkit/golden_toolkit.dart'; 8 | import 'package:json_data_explorer/json_data_explorer.dart'; 9 | import 'package:provider/provider.dart'; 10 | 11 | import 'test_data.dart'; 12 | 13 | void main() { 14 | late final dynamic jsonObject; 15 | 16 | setUpAll(() { 17 | jsonObject = json.decode(nobelPrizesJson); 18 | }); 19 | 20 | testGoldens('Customization', (tester) async { 21 | Widget buildWidget({ 22 | Key? key, 23 | bool collapseAll = false, 24 | void Function(DataExplorerStore store)? onStoreCreated, 25 | DataExplorerTheme? theme, 26 | NodeBuilder? rootInformationBuilder, 27 | NodeBuilder? collapsableToggleBuilder, 28 | NodeBuilder? trailingBuilder, 29 | Formatter? rootNameFormatter, 30 | Formatter? propertyNameFormatter, 31 | Formatter? valueFormatter, 32 | StyleBuilder? valueStyleBuilder, 33 | double itemSpacing = 2, 34 | }) => 35 | SizedBox( 36 | height: 200, 37 | child: ChangeNotifierProvider( 38 | create: (context) { 39 | final store = DataExplorerStore() 40 | ..buildNodes( 41 | jsonObject, 42 | areAllCollapsed: collapseAll, 43 | ); 44 | onStoreCreated?.call(store); 45 | return store; 46 | }, 47 | child: Consumer( 48 | builder: (context, state, child) => JsonDataExplorer( 49 | key: key, 50 | nodes: state.displayNodes, 51 | theme: theme, 52 | rootInformationBuilder: rootInformationBuilder, 53 | collapsableToggleBuilder: collapsableToggleBuilder, 54 | trailingBuilder: trailingBuilder, 55 | rootNameFormatter: rootNameFormatter, 56 | propertyNameFormatter: propertyNameFormatter, 57 | valueFormatter: valueFormatter, 58 | valueStyleBuilder: valueStyleBuilder, 59 | itemSpacing: itemSpacing, 60 | ), 61 | ), 62 | ), 63 | ); 64 | 65 | final builder = GoldenBuilder.grid( 66 | columns: 4, 67 | bgColor: Colors.white, 68 | widthToHeightRatio: 1, 69 | ) 70 | ..addScenario('Default', buildWidget()) 71 | ..addScenario( 72 | 'Expand', 73 | buildWidget( 74 | collapseAll: true, 75 | onStoreCreated: (store) => store.expandNode(store.displayNodes.first), 76 | ), 77 | ) 78 | ..addScenario( 79 | 'Root information', 80 | buildWidget( 81 | rootInformationBuilder: (context, node) => DecoratedBox( 82 | decoration: const BoxDecoration( 83 | color: Color(0x80E1E1E1), 84 | borderRadius: BorderRadius.all(Radius.circular(2)), 85 | ), 86 | child: Text( 87 | node.isClass 88 | ? '{${node.childrenCount}}' 89 | : '[${node.childrenCount}]', 90 | ), 91 | ), 92 | ), 93 | ) 94 | ..addScenario( 95 | 'Collapsable toggle', 96 | buildWidget( 97 | collapseAll: true, 98 | onStoreCreated: (store) => store.expandNode(store.displayNodes.first), 99 | collapsableToggleBuilder: (context, node) => node.isCollapsed 100 | ? const Icon(Icons.keyboard_arrow_up) 101 | : const Icon(Icons.keyboard_arrow_down), 102 | ), 103 | ) 104 | ..addScenario( 105 | 'Trailing builder', 106 | buildWidget( 107 | trailingBuilder: (context, node) => const Icon( 108 | Icons.info_outline, 109 | size: 18, 110 | color: Colors.black26, 111 | ), 112 | ), 113 | ) 114 | ..addScenario( 115 | 'Name formatters', 116 | buildWidget( 117 | rootNameFormatter: (dynamic name) => '$name', 118 | propertyNameFormatter: (dynamic name) => '$name =', 119 | valueFormatter: (dynamic value) => '"$value"', 120 | ), 121 | ) 122 | ..addScenario( 123 | 'Value style builder', 124 | buildWidget( 125 | valueStyleBuilder: (dynamic value, style) { 126 | final isInt = int.tryParse(value.toString()); 127 | return PropertyOverrides( 128 | style: isInt != null 129 | ? style.copyWith( 130 | color: Colors.blue, 131 | ) 132 | : style, 133 | ); 134 | }, 135 | ), 136 | ) 137 | ..addScenario('Item Spacing', buildWidget(itemSpacing: 10)); 138 | 139 | await tester.pumpWidgetBuilder( 140 | builder.build(), 141 | surfaceSize: const Size(1200, 600), 142 | ); 143 | await tester.pumpAndSettle(); 144 | await screenMatchesGolden(tester, 'data_explorer/customization'); 145 | }); 146 | 147 | testGoldens('Interaction', (tester) async { 148 | Widget buildWidget({ 149 | Key? key, 150 | bool collapseAll = false, 151 | }) => 152 | ChangeNotifierProvider( 153 | create: (context) => DataExplorerStore() 154 | ..buildNodes( 155 | jsonObject, 156 | areAllCollapsed: collapseAll, 157 | ), 158 | child: Consumer( 159 | builder: (context, state, child) => SizedBox( 160 | height: 400, 161 | child: JsonDataExplorer( 162 | key: key, 163 | nodes: state.displayNodes, 164 | ), 165 | ), 166 | ), 167 | ); 168 | 169 | const expandNodeKey = Key('expandNodeKey'); 170 | const mouseHoverKey = Key('mouseHoverKey'); 171 | const collapseNodeKey = Key('collapseNodeKey'); 172 | final builder = GoldenBuilder.grid( 173 | columns: 4, 174 | bgColor: Colors.white, 175 | widthToHeightRatio: 0.5, 176 | ) 177 | ..addScenario('All collapsed', buildWidget(collapseAll: true)) 178 | ..addScenario( 179 | 'Expand', 180 | buildWidget( 181 | key: expandNodeKey, 182 | collapseAll: true, 183 | ), 184 | ) 185 | ..addScenario( 186 | 'Collapse node', 187 | buildWidget( 188 | key: collapseNodeKey, 189 | ), 190 | ) 191 | ..addScenario( 192 | 'Mouse hover', 193 | buildWidget( 194 | key: mouseHoverKey, 195 | ), 196 | ); 197 | await tester.pumpWidgetBuilder( 198 | builder.build(), 199 | surfaceSize: const Size(1500, 400), 200 | ); 201 | 202 | await tester.tap( 203 | find.descendant( 204 | of: find.byKey(expandNodeKey), 205 | matching: find.text( 206 | 'prizes:', 207 | findRichText: true, 208 | ), 209 | ), 210 | ); 211 | 212 | await tester.tap( 213 | find 214 | .descendant( 215 | of: find.byKey(collapseNodeKey), 216 | matching: find.text( 217 | 'laureates:', 218 | findRichText: true, 219 | ), 220 | ) 221 | .first, 222 | ); 223 | 224 | final gesture = await tester.createGesture( 225 | kind: PointerDeviceKind.mouse, 226 | ); 227 | await gesture.addPointer(location: Offset.zero); 228 | addTearDown(gesture.removePointer); 229 | await tester.pump(); 230 | final finder = find.descendant( 231 | of: find.byKey(mouseHoverKey), 232 | matching: find.text( 233 | 'laureates:', 234 | findRichText: true, 235 | ), 236 | ); 237 | await gesture.moveTo(tester.getCenter(finder.first)); 238 | await tester.pumpAndSettle(); 239 | 240 | await screenMatchesGolden(tester, 'data_explorer/interaction'); 241 | }); 242 | } 243 | -------------------------------------------------------------------------------- /test/golden/multi_size_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:golden_toolkit/golden_toolkit.dart'; 5 | import 'package:json_data_explorer/json_data_explorer.dart'; 6 | import 'package:provider/provider.dart'; 7 | 8 | import 'test_data.dart'; 9 | 10 | void main() { 11 | testGoldens('Tree should look correct on multiple device sizes', 12 | (tester) async { 13 | final dynamic jsonObject = json.decode(nobelPrizesJson); 14 | 15 | final builder = DeviceBuilder() 16 | ..overrideDevicesForAllScenarios( 17 | devices: [ 18 | Device.phone, 19 | Device.iphone11, 20 | Device.tabletPortrait, 21 | Device.tabletLandscape, 22 | ], 23 | ) 24 | ..addScenario( 25 | name: 'default theme', 26 | widget: ChangeNotifierProvider( 27 | create: (context) => DataExplorerStore()..buildNodes(jsonObject), 28 | child: Consumer( 29 | builder: (context, state, child) => JsonDataExplorer( 30 | nodes: state.displayNodes, 31 | ), 32 | ), 33 | ), 34 | ) 35 | ..addScenario( 36 | name: 'custom theme', 37 | widget: ChangeNotifierProvider( 38 | create: (context) => DataExplorerStore()..buildNodes(jsonObject), 39 | child: Consumer( 40 | builder: (context, state, child) => JsonDataExplorer( 41 | nodes: state.displayNodes, 42 | theme: DataExplorerTheme( 43 | rootKeyTextStyle: const TextStyle( 44 | color: Colors.black, 45 | fontWeight: FontWeight.bold, 46 | fontSize: 18, 47 | ), 48 | propertyKeyTextStyle: TextStyle( 49 | color: Colors.black.withOpacity(0.7), 50 | fontWeight: FontWeight.bold, 51 | fontSize: 18, 52 | ), 53 | valueTextStyle: const TextStyle( 54 | color: Color(0xFFCA442C), 55 | fontSize: 18, 56 | ), 57 | indentationLineColor: const Color(0xFF515151), 58 | highlightColor: const Color(0xFFF1F1F1), 59 | ), 60 | ), 61 | ), 62 | ), 63 | ); 64 | 65 | await tester.pumpDeviceBuilder(builder); 66 | await screenMatchesGolden(tester, 'multi_size_test'); 67 | }); 68 | } 69 | -------------------------------------------------------------------------------- /test/unit/flatten_tests.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter_test/flutter_test.dart'; 4 | import 'package:json_data_explorer/json_data_explorer.dart'; 5 | 6 | void main() { 7 | List _buildList(String jsonString) { 8 | final builtNodes = buildViewModelNodes(json.decode(jsonString)); 9 | return flatten(builtNodes); 10 | } 11 | 12 | group('Flatten algorithm tests', () { 13 | group('Classes', () { 14 | test('builds a flat list from a simple json', () { 15 | const jsonString = ''' 16 | { 17 | "firstField": "firstField", 18 | "secondField": "secondField", 19 | "thirdField": "thirdField" 20 | } 21 | '''; 22 | 23 | final viewModels = _buildList(jsonString); 24 | expect(viewModels, hasLength(3)); 25 | expect(viewModels[0].key, 'firstField'); 26 | expect(viewModels[0].value, 'firstField'); 27 | expect(viewModels[0].isRoot, isFalse); 28 | expect(viewModels[0].treeDepth, 0); 29 | 30 | expect(viewModels[1].key, 'secondField'); 31 | expect(viewModels[1].value, 'secondField'); 32 | expect(viewModels[1].isRoot, isFalse); 33 | expect(viewModels[1].treeDepth, 0); 34 | 35 | expect(viewModels[2].key, 'thirdField'); 36 | expect(viewModels[2].value, 'thirdField'); 37 | expect(viewModels[2].isRoot, isFalse); 38 | expect(viewModels[2].treeDepth, 0); 39 | }); 40 | 41 | test('builds a flat list from multiple json classes', () { 42 | const jsonString = ''' 43 | { 44 | "firstClass": { 45 | "firstField": "firstField", 46 | "secondField": "secondField", 47 | "thirdField": "thirdField" 48 | }, 49 | "secondClass": { 50 | "firstField": "firstField", 51 | "secondField": "secondField", 52 | "thirdField": "thirdField" 53 | } 54 | } 55 | '''; 56 | 57 | final viewModels = _buildList(jsonString); 58 | expect(viewModels, hasLength(8)); 59 | expect(viewModels[0].key, 'firstClass'); 60 | expect(viewModels[0].value, isNotNull); 61 | expect(viewModels[0].isRoot, isTrue); 62 | expect(viewModels[0].isClass, isTrue); 63 | expect(viewModels[0].isArray, isFalse); 64 | expect(viewModels[0].childrenCount, 3); 65 | expect(viewModels[0].treeDepth, 0); 66 | 67 | expect(viewModels[1].key, 'firstField'); 68 | expect(viewModels[1].value, 'firstField'); 69 | expect(viewModels[1].isRoot, isFalse); 70 | expect(viewModels[1].treeDepth, 1); 71 | 72 | expect(viewModels[2].key, 'secondField'); 73 | expect(viewModels[2].value, 'secondField'); 74 | expect(viewModels[2].isRoot, isFalse); 75 | expect(viewModels[2].treeDepth, 1); 76 | 77 | expect(viewModels[3].key, 'thirdField'); 78 | expect(viewModels[3].value, 'thirdField'); 79 | expect(viewModels[3].isRoot, isFalse); 80 | expect(viewModels[3].treeDepth, 1); 81 | 82 | expect(viewModels[4].key, 'secondClass'); 83 | expect(viewModels[4].value, isNotNull); 84 | expect(viewModels[4].isRoot, isTrue); 85 | expect(viewModels[4].isClass, isTrue); 86 | expect(viewModels[4].isArray, isFalse); 87 | expect(viewModels[4].childrenCount, 3); 88 | expect(viewModels[4].treeDepth, 0); 89 | 90 | expect(viewModels[5].key, 'firstField'); 91 | expect(viewModels[5].value, 'firstField'); 92 | expect(viewModels[5].isRoot, isFalse); 93 | expect(viewModels[5].treeDepth, 1); 94 | 95 | expect(viewModels[6].key, 'secondField'); 96 | expect(viewModels[6].value, 'secondField'); 97 | expect(viewModels[6].isRoot, isFalse); 98 | expect(viewModels[6].treeDepth, 1); 99 | 100 | expect(viewModels[7].key, 'thirdField'); 101 | expect(viewModels[7].value, 'thirdField'); 102 | expect(viewModels[7].isRoot, isFalse); 103 | expect(viewModels[7].treeDepth, 1); 104 | }); 105 | 106 | test('builds a flat list from multiple nested json classes', () { 107 | const jsonString = ''' 108 | { 109 | "firstClass": { 110 | "firstField": "firstField", 111 | "firstClassField": { 112 | "firstField": "firstField", 113 | "innerClassField": { 114 | "firstField": "firstField" 115 | } 116 | }, 117 | "secondClassField": { 118 | "firstField": "firstField", 119 | "innerClassField": { 120 | "firstField": "firstField" 121 | } 122 | } 123 | } 124 | } 125 | '''; 126 | 127 | final viewModels = _buildList(jsonString); 128 | expect(viewModels, hasLength(10)); 129 | expect(viewModels[0].key, 'firstClass'); 130 | expect(viewModels[0].value, isNotNull); 131 | expect(viewModels[0].isRoot, isTrue); 132 | expect(viewModels[0].isClass, isTrue); 133 | expect(viewModels[0].isArray, isFalse); 134 | expect(viewModels[0].childrenCount, 3); 135 | expect(viewModels[0].treeDepth, 0); 136 | 137 | expect(viewModels[1].key, 'firstField'); 138 | expect(viewModels[1].value, isNotNull); 139 | expect(viewModels[1].isRoot, isFalse); 140 | expect(viewModels[1].treeDepth, 1); 141 | 142 | expect(viewModels[2].key, 'firstClassField'); 143 | expect(viewModels[2].value, isNotNull); 144 | expect(viewModels[2].isRoot, isTrue); 145 | expect(viewModels[2].isClass, isTrue); 146 | expect(viewModels[2].isArray, isFalse); 147 | expect(viewModels[2].childrenCount, 2); 148 | expect(viewModels[2].treeDepth, 1); 149 | 150 | expect(viewModels[3].key, 'firstField'); 151 | expect(viewModels[3].value, isNotNull); 152 | expect(viewModels[3].isRoot, isFalse); 153 | expect(viewModels[3].treeDepth, 2); 154 | 155 | expect(viewModels[4].key, 'innerClassField'); 156 | expect(viewModels[4].value, isNotNull); 157 | expect(viewModels[4].isRoot, isTrue); 158 | expect(viewModels[4].isClass, isTrue); 159 | expect(viewModels[4].isArray, isFalse); 160 | expect(viewModels[4].childrenCount, 1); 161 | expect(viewModels[4].treeDepth, 2); 162 | 163 | expect(viewModels[5].key, 'firstField'); 164 | expect(viewModels[5].value, isNotNull); 165 | expect(viewModels[5].isRoot, isFalse); 166 | expect(viewModels[5].treeDepth, 3); 167 | 168 | expect(viewModels[6].key, 'secondClassField'); 169 | expect(viewModels[6].value, isNotNull); 170 | expect(viewModels[6].isRoot, isTrue); 171 | expect(viewModels[6].isClass, isTrue); 172 | expect(viewModels[6].isArray, isFalse); 173 | expect(viewModels[6].childrenCount, 2); 174 | expect(viewModels[6].treeDepth, 1); 175 | 176 | expect(viewModels[7].key, 'firstField'); 177 | expect(viewModels[7].value, isNotNull); 178 | expect(viewModels[7].isRoot, isFalse); 179 | expect(viewModels[7].treeDepth, 2); 180 | 181 | expect(viewModels[8].key, 'innerClassField'); 182 | expect(viewModels[8].value, isNotNull); 183 | expect(viewModels[8].isRoot, isTrue); 184 | expect(viewModels[8].isClass, isTrue); 185 | expect(viewModels[8].isArray, isFalse); 186 | expect(viewModels[8].childrenCount, 1); 187 | expect(viewModels[8].treeDepth, 2); 188 | 189 | expect(viewModels[9].key, 'firstField'); 190 | expect(viewModels[9].value, isNotNull); 191 | expect(viewModels[9].isRoot, isFalse); 192 | expect(viewModels[9].treeDepth, 3); 193 | }); 194 | }); 195 | 196 | group('Arrays', () { 197 | test('builds a flat list from json array', () { 198 | const jsonString = '[1, 2]'; 199 | 200 | final viewModels = _buildList(jsonString); 201 | expect(viewModels, hasLength(3)); 202 | 203 | expect(viewModels[0].key, 'data'); 204 | expect(viewModels[0].value, isNotNull); 205 | expect(viewModels[0].isRoot, isTrue); 206 | expect(viewModels[0].isArray, isTrue); 207 | expect(viewModels[0].childrenCount, 2); 208 | expect(viewModels[0].treeDepth, 0); 209 | 210 | expect(viewModels[1].key, '0'); 211 | expect(viewModels[1].value, 1); 212 | expect(viewModels[1].isRoot, isFalse); 213 | expect(viewModels[1].treeDepth, 1); 214 | 215 | expect(viewModels[2].key, '1'); 216 | expect(viewModels[2].value, 2); 217 | expect(viewModels[2].isRoot, isFalse); 218 | expect(viewModels[2].treeDepth, 1); 219 | }); 220 | 221 | test('builds a flat list from array of classes', () { 222 | const jsonString = ''' 223 | [ 224 | { 225 | "0.firstField": "firstField" 226 | }, 227 | { 228 | "1.firstField": "firstField" 229 | } 230 | ] 231 | '''; 232 | 233 | final viewModels = _buildList(jsonString); 234 | expect(viewModels, hasLength(5)); 235 | expect(viewModels[0].key, 'data'); 236 | expect(viewModels[0].value, isNotNull); 237 | expect(viewModels[0].isRoot, isTrue); 238 | expect(viewModels[0].isArray, isTrue); 239 | expect(viewModels[0].childrenCount, 2); 240 | expect(viewModels[0].treeDepth, 0); 241 | 242 | expect(viewModels[1].key, '0'); 243 | expect(viewModels[1].value, isNotNull); 244 | expect(viewModels[1].isRoot, isTrue); 245 | expect(viewModels[1].isClass, isTrue); 246 | expect(viewModels[1].isArray, isFalse); 247 | expect(viewModels[1].childrenCount, 1); 248 | expect(viewModels[1].treeDepth, 1); 249 | 250 | expect(viewModels[2].key, '0.firstField'); 251 | expect(viewModels[2].value, isNotNull); 252 | expect(viewModels[2].isRoot, isFalse); 253 | expect(viewModels[2].treeDepth, 2); 254 | 255 | expect(viewModels[3].key, '1'); 256 | expect(viewModels[3].value, isNotNull); 257 | expect(viewModels[3].isRoot, isTrue); 258 | expect(viewModels[3].isClass, isTrue); 259 | expect(viewModels[3].isArray, isFalse); 260 | expect(viewModels[3].childrenCount, 1); 261 | expect(viewModels[3].treeDepth, 1); 262 | 263 | expect(viewModels[4].key, '1.firstField'); 264 | expect(viewModels[4].value, isNotNull); 265 | expect(viewModels[4].isRoot, isFalse); 266 | expect(viewModels[4].treeDepth, 2); 267 | }); 268 | 269 | test('builds a flat list from class with nested arrays', () { 270 | const jsonString = ''' 271 | { 272 | "firstClass": { 273 | "firstClass.firstField": "firstField", 274 | "firstClass.array": [ 275 | 1, 276 | 2 277 | ] 278 | }, 279 | "secondClass": { 280 | "secondClass.firstField": "firstField", 281 | "secondClass.array": [ 282 | 3, 283 | 4 284 | ] 285 | } 286 | } 287 | '''; 288 | 289 | final viewModels = _buildList(jsonString); 290 | expect(viewModels, hasLength(10)); 291 | expect(viewModels[0].key, 'firstClass'); 292 | expect(viewModels[0].value, isNotNull); 293 | expect(viewModels[0].isRoot, isTrue); 294 | expect(viewModels[0].isClass, isTrue); 295 | expect(viewModels[0].isArray, isFalse); 296 | expect(viewModels[0].childrenCount, 2); 297 | expect(viewModels[0].treeDepth, 0); 298 | 299 | expect(viewModels[1].key, 'firstClass.firstField'); 300 | expect(viewModels[1].value, 'firstField'); 301 | expect(viewModels[1].isRoot, isFalse); 302 | expect(viewModels[1].treeDepth, 1); 303 | 304 | expect(viewModels[2].key, 'firstClass.array'); 305 | expect(viewModels[2].value, isNotNull); 306 | expect(viewModels[2].isRoot, isTrue); 307 | expect(viewModels[2].isArray, isTrue); 308 | expect(viewModels[2].childrenCount, 2); 309 | expect(viewModels[2].treeDepth, 1); 310 | 311 | expect(viewModels[3].key, '0'); 312 | expect(viewModels[3].value, 1); 313 | expect(viewModels[3].isRoot, isFalse); 314 | expect(viewModels[3].treeDepth, 2); 315 | 316 | expect(viewModels[4].key, '1'); 317 | expect(viewModels[4].value, 2); 318 | expect(viewModels[4].isRoot, isFalse); 319 | expect(viewModels[4].treeDepth, 2); 320 | 321 | expect(viewModels[5].key, 'secondClass'); 322 | expect(viewModels[5].value, isNotNull); 323 | expect(viewModels[5].isRoot, isTrue); 324 | expect(viewModels[5].isClass, isTrue); 325 | expect(viewModels[5].isArray, isFalse); 326 | expect(viewModels[5].childrenCount, 2); 327 | expect(viewModels[5].treeDepth, 0); 328 | 329 | expect(viewModels[6].key, 'secondClass.firstField'); 330 | expect(viewModels[6].value, 'firstField'); 331 | expect(viewModels[6].isRoot, isFalse); 332 | expect(viewModels[6].treeDepth, 1); 333 | 334 | expect(viewModels[7].key, 'secondClass.array'); 335 | expect(viewModels[7].value, isNotNull); 336 | expect(viewModels[7].isRoot, isTrue); 337 | expect(viewModels[7].isArray, isTrue); 338 | expect(viewModels[7].childrenCount, 2); 339 | expect(viewModels[7].treeDepth, 1); 340 | 341 | expect(viewModels[8].key, '0'); 342 | expect(viewModels[8].value, 3); 343 | expect(viewModels[8].isRoot, isFalse); 344 | expect(viewModels[8].treeDepth, 2); 345 | 346 | expect(viewModels[9].key, '1'); 347 | expect(viewModels[9].value, 4); 348 | expect(viewModels[9].isRoot, isFalse); 349 | expect(viewModels[9].treeDepth, 2); 350 | }); 351 | }); 352 | }); 353 | } 354 | -------------------------------------------------------------------------------- /test/unit/node_view_model_state_test.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: avoid_dynamic_calls 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:json_data_explorer/json_data_explorer.dart'; 4 | import 'package:mocktail/mocktail.dart'; 5 | 6 | class MockCallbackFunction extends Mock { 7 | void call(); 8 | } 9 | 10 | void main() { 11 | group('NodeViewModelState', () { 12 | group('Property', () { 13 | test('build as a property', () { 14 | final viewModel = NodeViewModelState.fromProperty( 15 | treeDepth: 1, 16 | key: 'key', 17 | value: 123, 18 | parent: null, 19 | ); 20 | 21 | expect(viewModel.key, 'key'); 22 | expect(viewModel.value, isA()); 23 | expect(viewModel.value, 123); 24 | expect(viewModel.isRoot, isFalse); 25 | expect(viewModel.isClass, isFalse); 26 | expect(viewModel.isArray, isFalse); 27 | expect(viewModel.isHighlighted, isFalse); 28 | expect(viewModel.isCollapsed, isFalse); 29 | expect(viewModel.parent, isNull); 30 | }); 31 | 32 | test('a property has no children nodes', () { 33 | final parent = NodeViewModelState.fromArray( 34 | treeDepth: 1, 35 | key: 'parentKey', 36 | parent: null, 37 | ); 38 | 39 | final viewModel = NodeViewModelState.fromProperty( 40 | treeDepth: 1, 41 | key: 'key', 42 | value: 123, 43 | parent: parent, 44 | ); 45 | 46 | expect(viewModel.childrenCount, 0); 47 | expect(viewModel.children, hasLength(0)); 48 | expect(viewModel.parent!.key, 'parentKey'); 49 | }); 50 | 51 | test('highlight notifies listeners', () { 52 | final viewModel = NodeViewModelState.fromProperty( 53 | treeDepth: 1, 54 | key: 'key', 55 | value: 123, 56 | parent: null, 57 | ); 58 | final listener = MockCallbackFunction(); 59 | viewModel.addListener(listener); 60 | 61 | viewModel.highlight(); 62 | expect(viewModel.isHighlighted, isTrue); 63 | 64 | viewModel.highlight(isHighlighted: false); 65 | expect(viewModel.isHighlighted, isFalse); 66 | verify(listener.call).called(2); 67 | }); 68 | 69 | test('collapse notifies listeners', () { 70 | final viewModel = NodeViewModelState.fromProperty( 71 | treeDepth: 1, 72 | key: 'key', 73 | value: 123, 74 | parent: null, 75 | ); 76 | final listener = MockCallbackFunction(); 77 | viewModel.addListener(listener); 78 | 79 | viewModel.collapse(); 80 | expect(viewModel.isCollapsed, isTrue); 81 | 82 | viewModel.expand(); 83 | expect(viewModel.isCollapsed, isFalse); 84 | verify(listener.call).called(2); 85 | }); 86 | }); 87 | 88 | group('Class', () { 89 | test('build as a class', () { 90 | final viewModel = NodeViewModelState.fromClass( 91 | treeDepth: 0, 92 | key: 'classKey', 93 | parent: null, 94 | ); 95 | 96 | final classMap = { 97 | 'propertyA': NodeViewModelState.fromProperty( 98 | treeDepth: 1, 99 | key: 'propertyA', 100 | value: 123, 101 | parent: viewModel, 102 | ), 103 | 'propertyB': NodeViewModelState.fromProperty( 104 | treeDepth: 1, 105 | key: 'propertyB', 106 | value: 'string', 107 | parent: viewModel, 108 | ), 109 | }; 110 | 111 | viewModel.value = classMap; 112 | 113 | expect(viewModel.key, 'classKey'); 114 | expect(viewModel.value, isA>()); 115 | expect(viewModel.value, hasLength(2)); 116 | expect(viewModel.isRoot, isTrue); 117 | expect(viewModel.isClass, isTrue); 118 | expect(viewModel.isArray, isFalse); 119 | expect(viewModel.isHighlighted, isFalse); 120 | expect(viewModel.isCollapsed, isFalse); 121 | 122 | expect(classMap['propertyA']!.parent!.key, 'classKey'); 123 | }); 124 | 125 | test('children nodes', () { 126 | final viewModel = NodeViewModelState.fromClass( 127 | treeDepth: 0, 128 | key: 'classKey', 129 | parent: null, 130 | ); 131 | 132 | final classMap = { 133 | 'propertyA': NodeViewModelState.fromProperty( 134 | treeDepth: 1, 135 | key: 'propertyA', 136 | value: 123, 137 | parent: viewModel, 138 | ), 139 | 'propertyB': NodeViewModelState.fromProperty( 140 | treeDepth: 1, 141 | key: 'propertyB', 142 | value: 'string', 143 | parent: viewModel, 144 | ), 145 | }; 146 | 147 | viewModel.value = classMap; 148 | 149 | expect(viewModel.childrenCount, 2); 150 | expect(viewModel.children, hasLength(2)); 151 | expect(viewModel.children.elementAt(0).key, 'propertyA'); 152 | expect(viewModel.children.elementAt(1).key, 'propertyB'); 153 | }); 154 | 155 | test('highlight sets highlight in all children', () { 156 | final viewModel = NodeViewModelState.fromClass( 157 | treeDepth: 0, 158 | key: 'classKey', 159 | parent: null, 160 | ); 161 | 162 | final subClass = NodeViewModelState.fromClass( 163 | treeDepth: 1, 164 | key: 'innerClass', 165 | parent: viewModel, 166 | ); 167 | 168 | final classMap = { 169 | 'property': NodeViewModelState.fromProperty( 170 | treeDepth: 1, 171 | key: 'property', 172 | value: 123, 173 | parent: viewModel, 174 | ), 175 | 'innerClass': subClass, 176 | }; 177 | 178 | subClass.value = { 179 | 'innerClassProperty': NodeViewModelState.fromProperty( 180 | treeDepth: 2, 181 | key: 'innerClassProperty', 182 | value: 123, 183 | parent: classMap['innerClass'], 184 | ), 185 | }; 186 | 187 | viewModel.value = classMap; 188 | viewModel.highlight(); 189 | 190 | expect(viewModel.isHighlighted, isTrue); 191 | expect(classMap['property']!.isHighlighted, isTrue); 192 | expect(classMap['innerClass']!.isHighlighted, isTrue); 193 | expect( 194 | classMap['innerClass']!.value['innerClassProperty']!.isHighlighted, 195 | isTrue, 196 | ); 197 | 198 | viewModel.highlight(isHighlighted: false); 199 | expect(viewModel.isHighlighted, isFalse); 200 | expect(classMap['property']!.isHighlighted, isFalse); 201 | expect(classMap['innerClass']!.isHighlighted, isFalse); 202 | expect( 203 | classMap['innerClass']!.value['innerClassProperty']!.isHighlighted, 204 | isFalse, 205 | ); 206 | }); 207 | }); 208 | 209 | group('Array', () { 210 | test('build as an array', () { 211 | final viewModel = NodeViewModelState.fromArray( 212 | treeDepth: 0, 213 | key: 'arrayKey', 214 | parent: null, 215 | ); 216 | 217 | final arrayValues = [ 218 | NodeViewModelState.fromProperty( 219 | treeDepth: 1, 220 | key: '0', 221 | value: 123, 222 | parent: viewModel, 223 | ), 224 | NodeViewModelState.fromProperty( 225 | treeDepth: 1, 226 | key: '1', 227 | value: 'string', 228 | parent: viewModel, 229 | ), 230 | ]; 231 | 232 | viewModel.value = arrayValues; 233 | 234 | expect(viewModel.key, 'arrayKey'); 235 | expect(viewModel.value, isA>()); 236 | expect(viewModel.value, hasLength(2)); 237 | expect(viewModel.isRoot, isTrue); 238 | expect(viewModel.isClass, isFalse); 239 | expect(viewModel.isArray, isTrue); 240 | expect(viewModel.isHighlighted, isFalse); 241 | expect(viewModel.isCollapsed, isFalse); 242 | }); 243 | 244 | test('children nodes', () { 245 | final viewModel = NodeViewModelState.fromArray( 246 | treeDepth: 0, 247 | key: 'arrayKey', 248 | parent: null, 249 | ); 250 | 251 | final arrayValues = [ 252 | NodeViewModelState.fromProperty( 253 | treeDepth: 1, 254 | key: '0', 255 | value: 123, 256 | parent: viewModel, 257 | ), 258 | NodeViewModelState.fromProperty( 259 | treeDepth: 1, 260 | key: '1', 261 | value: 'string', 262 | parent: viewModel, 263 | ), 264 | ]; 265 | 266 | viewModel.value = arrayValues; 267 | 268 | expect(viewModel.childrenCount, 2); 269 | expect(viewModel.children, hasLength(2)); 270 | expect(viewModel.children.elementAt(0).key, '0'); 271 | expect(viewModel.children.elementAt(1).key, '1'); 272 | }); 273 | 274 | test('highlight sets highlight in all children', () { 275 | final viewModel = NodeViewModelState.fromArray( 276 | treeDepth: 0, 277 | key: 'arrayKey', 278 | parent: null, 279 | ); 280 | 281 | final subClass = NodeViewModelState.fromClass( 282 | treeDepth: 1, 283 | key: 'class', 284 | parent: viewModel, 285 | ); 286 | 287 | subClass.value = { 288 | 'classProperty': NodeViewModelState.fromProperty( 289 | treeDepth: 2, 290 | key: 'classProperty', 291 | value: 123, 292 | parent: subClass, 293 | ), 294 | }; 295 | 296 | final arrayValues = [subClass]; 297 | 298 | viewModel.value = arrayValues; 299 | viewModel.highlight(); 300 | 301 | expect(viewModel.isHighlighted, isTrue); 302 | expect(arrayValues[0].isHighlighted, isTrue); 303 | expect(arrayValues[0].value['classProperty']!.isHighlighted, isTrue); 304 | 305 | viewModel.highlight(isHighlighted: false); 306 | expect(viewModel.isHighlighted, isFalse); 307 | expect(arrayValues[0].isHighlighted, isFalse); 308 | expect(arrayValues[0].value['classProperty']!.isHighlighted, isFalse); 309 | }); 310 | }); 311 | 312 | group('parent nodes', () { 313 | test('work properly', () { 314 | final firstClass = NodeViewModelState.fromClass( 315 | treeDepth: 0, 316 | key: 'firstClass', 317 | parent: null, 318 | ); 319 | 320 | final firstClassFirstField = NodeViewModelState.fromClass( 321 | treeDepth: 1, 322 | key: 'firstClass.firstField', 323 | parent: firstClass, 324 | ); 325 | 326 | final firstClassFirstClassField = NodeViewModelState.fromClass( 327 | treeDepth: 2, 328 | key: 'firstClass.firstClassField', 329 | parent: firstClassFirstField, 330 | ); 331 | 332 | final firstClassFirstClassFieldArray = NodeViewModelState.fromArray( 333 | treeDepth: 3, 334 | key: 'firstClass.firstClassField.array', 335 | parent: firstClassFirstClassField, 336 | ); 337 | 338 | final firstClassFirstClassFieldArrayField = 339 | NodeViewModelState.fromProperty( 340 | treeDepth: 4, 341 | key: 'key', 342 | value: 'value', 343 | parent: firstClassFirstClassFieldArray, 344 | ); 345 | 346 | firstClass.value = firstClassFirstField; 347 | firstClassFirstField.value = firstClassFirstClassField; 348 | firstClassFirstClassField.value = firstClassFirstClassFieldArray; 349 | firstClassFirstClassFieldArray.value = 350 | firstClassFirstClassFieldArrayField; 351 | 352 | expect(firstClass.parent, isNull); 353 | expect(firstClassFirstField.parent, firstClass); 354 | expect(firstClassFirstClassField.parent, firstClassFirstField); 355 | expect( 356 | firstClassFirstClassFieldArray.parent, 357 | firstClassFirstClassField, 358 | ); 359 | expect( 360 | firstClassFirstClassFieldArrayField.parent, 361 | firstClassFirstClassFieldArray, 362 | ); 363 | }); 364 | }); 365 | }); 366 | } 367 | --------------------------------------------------------------------------------