├── linux ├── .gitignore ├── main.cc ├── flutter │ ├── generated_plugin_registrant.h │ ├── generated_plugins.cmake │ ├── generated_plugin_registrant.cc │ └── CMakeLists.txt ├── my_application.h ├── my_application.cc └── CMakeLists.txt ├── .fvmrc ├── ios ├── Runner │ ├── Runner-Bridging-Header.h │ ├── Assets.xcassets │ │ ├── LaunchImage.imageset │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ ├── README.md │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ ├── 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-50x50@1x.png │ │ │ ├── Icon-App-50x50@2x.png │ │ │ ├── Icon-App-57x57@1x.png │ │ │ ├── Icon-App-57x57@2x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-72x72@1x.png │ │ │ ├── Icon-App-72x72@2x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard │ └── Info.plist ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist ├── RunnerTests │ └── RunnerTests.swift ├── .gitignore └── Podfile ├── .vscode ├── settings.json └── launch.json ├── web ├── favicon.png ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ └── Icon-maskable-512.png ├── manifest.json └── index.html ├── assets └── icons │ ├── icon.png │ └── installer_icon.ico ├── macos ├── Runner │ ├── Configs │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ ├── Warnings.xcconfig │ │ └── AppInfo.xcconfig │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── app_icon_128.png │ │ │ ├── app_icon_16.png │ │ │ ├── app_icon_256.png │ │ │ ├── app_icon_32.png │ │ │ ├── app_icon_512.png │ │ │ ├── app_icon_64.png │ │ │ ├── app_icon_1024.png │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── Release.entitlements │ ├── DebugProfile.entitlements │ ├── MainFlutterWindow.swift │ └── Info.plist ├── .gitignore ├── Flutter │ ├── Flutter-Debug.xcconfig │ ├── Flutter-Release.xcconfig │ └── GeneratedPluginRegistrant.swift ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── Runner.xcodeproj │ ├── project.xcworkspace │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── RunnerTests │ └── RunnerTests.swift ├── Podfile └── Podfile.lock ├── windows ├── runner │ ├── resources │ │ └── app_icon.ico │ ├── resource.h │ ├── utils.h │ ├── runner.exe.manifest │ ├── flutter_window.h │ ├── main.cpp │ ├── CMakeLists.txt │ ├── utils.cpp │ ├── flutter_window.cpp │ ├── Runner.rc │ ├── win32_window.h │ └── win32_window.cpp ├── packaging │ └── exe │ │ ├── make_config.yaml │ │ └── inno-script.iss ├── .gitignore ├── flutter │ ├── generated_plugin_registrant.h │ ├── generated_plugins.cmake │ ├── generated_plugin_registrant.cc │ └── CMakeLists.txt └── CMakeLists.txt ├── android ├── gradle.properties ├── app │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ └── launcher_icon.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ └── launcher_icon.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ └── launcher_icon.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ └── launcher_icon.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ └── launcher_icon.png │ │ │ │ ├── drawable │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable-v21 │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── values │ │ │ │ │ └── styles.xml │ │ │ │ └── values-night │ │ │ │ │ └── styles.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── rivafarabi │ │ │ │ │ └── jsontry │ │ │ │ │ └── MainActivity.kt │ │ │ └── AndroidManifest.xml │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ └── profile │ │ │ └── AndroidManifest.xml │ └── build.gradle ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── .gitignore ├── build.gradle └── settings.gradle ├── devtools_options.yaml ├── lib ├── utils │ ├── snackbar.dart │ ├── path_utils.dart │ ├── style_cache.dart │ ├── search_controller.dart │ └── app_color_scheme.dart ├── models │ └── json_node.dart ├── main.dart └── widgets │ ├── context_menu_button.dart │ ├── json_tree_view.dart │ ├── macos_platform_menu.dart │ ├── json_node_row.dart │ ├── status_bar.dart │ ├── search_bar_widget.dart │ └── windows_platform_menu.dart ├── test ├── widget_test.dart └── json_provider_optimization_test.dart ├── .gitignore ├── distribute_options.yaml ├── LICENSE ├── flutter_launcher_icons.yaml ├── analysis_options.yaml ├── .metadata ├── sample.json ├── README.md └── pubspec.yaml /linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /.fvmrc: -------------------------------------------------------------------------------- 1 | { 2 | "flutter": "3.29.0" 3 | } -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "dart.flutterSdkPath": ".fvm/versions/3.29.0" 3 | } -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/web/favicon.png -------------------------------------------------------------------------------- /assets/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/assets/icons/icon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/web/icons/Icon-512.png -------------------------------------------------------------------------------- /assets/icons/installer_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/assets/icons/installer_icon.ico -------------------------------------------------------------------------------- /web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/dgph 7 | **/xcuserdata/ 8 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/android/app/src/main/res/mipmap-hdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/android/app/src/main/res/mipmap-mdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/jsontry/main/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/rivafarabi/jsontry/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.rivafarabi.jsontry 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() 6 | -------------------------------------------------------------------------------- /devtools_options.yaml: -------------------------------------------------------------------------------- 1 | description: This file stores settings for Dart & Flutter DevTools. 2 | documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states 3 | extensions: 4 | -------------------------------------------------------------------------------- /linux/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip 6 | -------------------------------------------------------------------------------- /macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @NSApplicationMain 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/utils/snackbar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | void showSnackBar(BuildContext context, String message) { 4 | ScaffoldMessenger.of(context).showSnackBar( 5 | SnackBar( 6 | content: Text(message), 7 | duration: const Duration(seconds: 2), 8 | ), 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /windows/packaging/exe/make_config.yaml: -------------------------------------------------------------------------------- 1 | app_id: c2b9ce7c-27d8-51f2-b675-7371151cb6ea 2 | publisher: Riva Farabi (Bigvaria) 3 | publisher_url: https://bigvaria.com 4 | display_name: JSONTry 5 | create_desktop_icon: true 6 | script_template: inno-script.iss 7 | setup_icon_file: assets\icons\installer_icon.ico 8 | locales: 9 | - en 10 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /macos/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /linux/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 fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | rootProject.buildDir = "../build" 9 | subprojects { 10 | project.buildDir = "${rootProject.buildDir}/${project.name}" 11 | } 12 | subprojects { 13 | project.evaluationDependsOn(":app") 14 | } 15 | 16 | tasks.register("clean", Delete) { 17 | delete rootProject.buildDir 18 | } 19 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.server 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lib/utils/path_utils.dart: -------------------------------------------------------------------------------- 1 | List getPathSegments(String path) { 2 | if (path.isEmpty) return []; 3 | final segments = []; 4 | final regex = RegExp(r'([^\.\[\]]+)|\[(\d+)\]'); 5 | for (final match in regex.allMatches(path)) { 6 | if (match.group(1) != null) { 7 | segments.add(match.group(1)!); 8 | } else if (match.group(2) != null) { 9 | segments.add(int.parse(match.group(2)!)); 10 | } 11 | } 12 | return segments; 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 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 | -------------------------------------------------------------------------------- /linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | 3 | import 'package:jsontry/main.dart'; 4 | 5 | void main() { 6 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 7 | // Build our app and trigger a frame. 8 | await tester.pumpWidget(const JsonTryApp()); 9 | 10 | // Verify that our counter starts at zero. 11 | expect(find.text('0'), findsNothing); 12 | expect(find.text('No JSON file loaded'), findsOneWidget); 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/utils/style_cache.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/painting.dart'; 2 | 3 | class StyleCache { 4 | late TextStyle baseStyle; 5 | late TextStyle keyStyle; 6 | late TextStyle colonStyle; 7 | 8 | void updateStyles() { 9 | baseStyle = const TextStyle( 10 | fontFamily: 'SF Mono', 11 | fontSize: 11, 12 | ); 13 | 14 | keyStyle = baseStyle.copyWith( 15 | fontWeight: FontWeight.w600, 16 | fontSize: 11, 17 | ); 18 | 19 | colonStyle = baseStyle.copyWith( 20 | fontSize: 11, 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = JSONTry 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.rivafarabi.jsontry 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2025 Riva Farabi All rights reserved. 15 | 16 | // Deployment target 17 | MACOSX_DEPLOYMENT_TARGET = 10.15 18 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | 8 | { 9 | "name": "jsontry", 10 | "request": "launch", 11 | "type": "dart" 12 | }, 13 | { 14 | "name": "jsontry (profile mode)", 15 | "request": "launch", 16 | "type": "dart", 17 | "flutterMode": "profile" 18 | }, 19 | { 20 | "name": "jsontry (release mode)", 21 | "request": "launch", 22 | "type": "dart", 23 | "flutterMode": "release" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | }() 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | plugins { 20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 21 | id "com.android.application" version "7.3.0" apply false 22 | id "org.jetbrains.kotlin.android" version "1.7.10" apply false 23 | } 24 | 25 | include ":app" 26 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Symbolication related 35 | app.*.symbols 36 | 37 | # Obfuscation related 38 | app.*.map.json 39 | 40 | # Android Studio will place build artifacts here 41 | /android/app/debug 42 | /android/app/profile 43 | /android/app/release 44 | 45 | # FVM Version Cache 46 | .fvm/ 47 | 48 | .dist/ -------------------------------------------------------------------------------- /distribute_options.yaml: -------------------------------------------------------------------------------- 1 | output: dist/ 2 | artifact_name: "JSONTry_Setup_{{build_name}}{{#ext}}.{{ext}}{{/ext}}" 3 | releases: 4 | - name: build-staging 5 | jobs: 6 | - name: build-staging-windows 7 | package: 8 | platform: windows 9 | target: exe 10 | - name: build-staging-macos 11 | package: 12 | platform: macos 13 | target: dmg 14 | - name: release-production 15 | jobs: 16 | - name: release-prod-windows 17 | package: 18 | platform: windows 19 | target: exe 20 | publish: 21 | target: github 22 | args: 23 | repo-owner: rivafarabi 24 | repo-name: jsontry 25 | - name: release-prod-macos 26 | package: 27 | platform: macos 28 | target: dmg 29 | publish: 30 | target: github 31 | args: 32 | repo-owner: rivafarabi 33 | repo-name: jsontry -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | desktop_drop 7 | native_context_menu 8 | screen_retriever_linux 9 | url_launcher_linux 10 | window_manager 11 | ) 12 | 13 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 14 | ) 15 | 16 | set(PLUGIN_BUNDLED_LIBRARIES) 17 | 18 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 19 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 20 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 21 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 22 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 23 | endforeach(plugin) 24 | 25 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 26 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 27 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 28 | endforeach(ffi_plugin) 29 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | desktop_drop 7 | native_context_menu 8 | screen_retriever_windows 9 | url_launcher_windows 10 | window_manager 11 | ) 12 | 13 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 14 | ) 15 | 16 | set(PLUGIN_BUNDLED_LIBRARIES) 17 | 18 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 19 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 20 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 21 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 22 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 23 | endforeach(plugin) 24 | 25 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 26 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 27 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 28 | endforeach(ffi_plugin) 29 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsontry", 3 | "short_name": "jsontry", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#hexcode", 7 | "theme_color": "#hexcode", 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 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Riva Farabi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /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 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | void RegisterPlugins(flutter::PluginRegistry* registry) { 16 | DesktopDropPluginRegisterWithRegistrar( 17 | registry->GetRegistrarForPlugin("DesktopDropPlugin")); 18 | NativeContextMenuPluginRegisterWithRegistrar( 19 | registry->GetRegistrarForPlugin("NativeContextMenuPlugin")); 20 | ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar( 21 | registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi")); 22 | UrlLauncherWindowsRegisterWithRegistrar( 23 | registry->GetRegistrarForPlugin("UrlLauncherWindows")); 24 | WindowManagerPluginRegisterWithRegistrar( 25 | registry->GetRegistrarForPlugin("WindowManagerPlugin")); 26 | } 27 | -------------------------------------------------------------------------------- /flutter_launcher_icons.yaml: -------------------------------------------------------------------------------- 1 | # flutter pub run flutter_launcher_icons 2 | flutter_launcher_icons: 3 | image_path: "assets/icons/icon.png" 4 | 5 | android: "launcher_icon" 6 | # image_path_android: "assets/icon/icon.png" 7 | min_sdk_android: 21 # android min sdk min:16, default 21 8 | # adaptive_icon_background: "assets/icon/background.png" 9 | # adaptive_icon_foreground: "assets/icon/foreground.png" 10 | # adaptive_icon_foreground_inset: 16 11 | # adaptive_icon_monochrome: "assets/icon/monochrome.png" 12 | 13 | ios: true 14 | # image_path_ios: "assets/icon/icon.png" 15 | remove_alpha_ios: true 16 | # image_path_ios_dark_transparent: "assets/icon/icon_dark.png" 17 | # image_path_ios_tinted_grayscale: "assets/icon/icon_tinted.png" 18 | # desaturate_tinted_to_grayscale_ios: true 19 | # background_color_ios: "#ffffff" 20 | 21 | web: 22 | generate: true 23 | image_path: "assets/icons/icon.png" 24 | background_color: "#hexcode" 25 | theme_color: "#hexcode" 26 | 27 | windows: 28 | generate: true 29 | image_path: "assets/icons/icon.png" 30 | icon_size: 48 31 | 32 | macos: 33 | generate: true 34 | image_path: "assets/icons/icon.png" 35 | -------------------------------------------------------------------------------- /macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import appkit_ui_element_colors 9 | import desktop_drop 10 | import macos_ui 11 | import macos_window_utils 12 | import native_context_menu 13 | import package_info_plus 14 | import screen_retriever_macos 15 | import url_launcher_macos 16 | import window_manager 17 | 18 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 19 | AppkitUiElementColorsPlugin.register(with: registry.registrar(forPlugin: "AppkitUiElementColorsPlugin")) 20 | DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin")) 21 | MacOSUiPlugin.register(with: registry.registrar(forPlugin: "MacOSUiPlugin")) 22 | MacOSWindowUtilsPlugin.register(with: registry.registrar(forPlugin: "MacOSWindowUtilsPlugin")) 23 | NativeContextMenuPlugin.register(with: registry.registrar(forPlugin: "NativeContextMenuPlugin")) 24 | FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) 25 | ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin")) 26 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) 27 | WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) 28 | } 29 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | fastjson 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /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.Create(L"JSONTry", 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 | -------------------------------------------------------------------------------- /lib/utils/search_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:jsontry/models/json_node.dart'; 2 | 3 | class SearchController { 4 | List search(List nodes, String query) { 5 | if (query.length < 2) return []; 6 | 7 | final results = []; 8 | final lowerQuery = query.toLowerCase(); 9 | 10 | // Use a simple iterative approach instead of building indexes 11 | _searchInNodes(nodes, lowerQuery, results); 12 | 13 | return results; 14 | } 15 | 16 | void _searchInNodes(List nodes, String query, List results) { 17 | for (final node in nodes) { 18 | // Check if key or value matches 19 | bool matches = false; 20 | 21 | // Check key match 22 | if (node.key?.toLowerCase().contains(query) ?? false) { 23 | matches = true; 24 | } 25 | 26 | // Check value match (for primitives only to avoid memory overhead) 27 | if (!matches && node.type != JsonNodeType.object && node.type != JsonNodeType.array) { 28 | if (node.value.toString().toLowerCase().contains(query)) { 29 | matches = true; 30 | } 31 | } 32 | 33 | if (matches) { 34 | results.add(node.path); 35 | } 36 | 37 | // Recursively search children without building indexes 38 | if (node.children != null) { 39 | _searchInNodes(node.children!, query, results); 40 | } 41 | } 42 | } 43 | 44 | void clear() { 45 | // No memory structures to clear in this simple implementation 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at https://dart.dev/lints. 17 | # 18 | # Instead of disabling a lint rule for the entire project in the 19 | # section below, it can also be suppressed for a single line of code 20 | # or a specific dart file by using the `// ignore: name_of_lint` and 21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 22 | # producing the lint. 23 | rules: 24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 26 | 27 | # Additional information about this file can be found at 28 | # https://dart.dev/guides/language/analysis-options 29 | -------------------------------------------------------------------------------- /macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.15' 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 | target 'RunnerTests' do 35 | inherit! :search_paths 36 | end 37 | end 38 | 39 | post_install do |installer| 40 | installer.pods_project.targets.each do |target| 41 | flutter_additional_macos_build_settings(target) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '12.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 | target 'RunnerTests' do 36 | inherit! :search_paths 37 | end 38 | end 39 | 40 | post_install do |installer| 41 | installer.pods_project.targets.each do |target| 42 | flutter_additional_ios_build_settings(target) 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /linux/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 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | void fl_register_plugins(FlPluginRegistry* registry) { 16 | g_autoptr(FlPluginRegistrar) desktop_drop_registrar = 17 | fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopDropPlugin"); 18 | desktop_drop_plugin_register_with_registrar(desktop_drop_registrar); 19 | g_autoptr(FlPluginRegistrar) native_context_menu_registrar = 20 | fl_plugin_registry_get_registrar_for_plugin(registry, "NativeContextMenuPlugin"); 21 | native_context_menu_plugin_register_with_registrar(native_context_menu_registrar); 22 | g_autoptr(FlPluginRegistrar) screen_retriever_linux_registrar = 23 | fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverLinuxPlugin"); 24 | screen_retriever_linux_plugin_register_with_registrar(screen_retriever_linux_registrar); 25 | g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = 26 | fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); 27 | url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); 28 | g_autoptr(FlPluginRegistrar) window_manager_registrar = 29 | fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin"); 30 | window_manager_plugin_register_with_registrar(window_manager_registrar); 31 | } 32 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | JSONtry 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | JSONtry 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | CADisableMinimumFrameDurationOnPhone 45 | 46 | UIApplicationSupportsIndirectInputEvents 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /lib/models/json_node.dart: -------------------------------------------------------------------------------- 1 | class JsonNode { 2 | final String? key; 3 | final dynamic value; 4 | final JsonNodeType type; 5 | final int depth; 6 | final String path; 7 | final bool isExpanded; 8 | final bool isSelected; 9 | final List? children; 10 | 11 | JsonNode({ 12 | this.key, 13 | required this.value, 14 | required this.type, 15 | required this.depth, 16 | required this.path, 17 | this.isExpanded = false, 18 | this.isSelected = false, 19 | this.children, 20 | }); 21 | 22 | bool get isCollapsible { 23 | if (type == JsonNodeType.object) { 24 | return value is Map && (value as Map).isNotEmpty; 25 | } else if (type == JsonNodeType.array) { 26 | return value is List && (value as List).isNotEmpty; 27 | } else { 28 | return false; 29 | } 30 | } 31 | 32 | JsonNode copyWith({ 33 | String? key, 34 | dynamic value, 35 | JsonNodeType? type, 36 | int? depth, 37 | String? path, 38 | bool? isExpanded, 39 | bool? isSelected, 40 | List? children, 41 | }) { 42 | return JsonNode( 43 | key: key ?? this.key, 44 | value: value ?? this.value, 45 | type: type ?? this.type, 46 | depth: depth ?? this.depth, 47 | path: path ?? this.path, 48 | isExpanded: isExpanded ?? this.isExpanded, 49 | isSelected: isSelected ?? this.isSelected, 50 | children: children ?? this.children, 51 | ); 52 | } 53 | 54 | JsonNode withoutChildren() { 55 | return JsonNode( 56 | key: key, 57 | value: value, 58 | type: type, 59 | depth: depth, 60 | path: path, 61 | isExpanded: isExpanded, 62 | ); 63 | } 64 | } 65 | 66 | enum JsonNodeType { 67 | object, 68 | array, 69 | string, 70 | number, 71 | boolean, 72 | nullValue, 73 | } 74 | -------------------------------------------------------------------------------- /.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: "5dcb86f68f239346676ceb1ed1ea385bd215fba1" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 17 | base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 18 | - platform: android 19 | create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 20 | base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 21 | - platform: ios 22 | create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 23 | base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 24 | - platform: linux 25 | create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 26 | base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 27 | - platform: macos 28 | create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 29 | base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 30 | - platform: web 31 | create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 32 | base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 33 | - platform: windows 34 | create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 35 | base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "version": 1, 4 | "author": "xcode" 5 | }, 6 | "images": [ 7 | { 8 | "size": "16x16", 9 | "idiom": "mac", 10 | "filename": "app_icon_16.png", 11 | "scale": "1x" 12 | }, 13 | { 14 | "size": "16x16", 15 | "idiom": "mac", 16 | "filename": "app_icon_32.png", 17 | "scale": "2x" 18 | }, 19 | { 20 | "size": "32x32", 21 | "idiom": "mac", 22 | "filename": "app_icon_32.png", 23 | "scale": "1x" 24 | }, 25 | { 26 | "size": "32x32", 27 | "idiom": "mac", 28 | "filename": "app_icon_64.png", 29 | "scale": "2x" 30 | }, 31 | { 32 | "size": "128x128", 33 | "idiom": "mac", 34 | "filename": "app_icon_128.png", 35 | "scale": "1x" 36 | }, 37 | { 38 | "size": "128x128", 39 | "idiom": "mac", 40 | "filename": "app_icon_256.png", 41 | "scale": "2x" 42 | }, 43 | { 44 | "size": "256x256", 45 | "idiom": "mac", 46 | "filename": "app_icon_256.png", 47 | "scale": "1x" 48 | }, 49 | { 50 | "size": "256x256", 51 | "idiom": "mac", 52 | "filename": "app_icon_512.png", 53 | "scale": "2x" 54 | }, 55 | { 56 | "size": "512x512", 57 | "idiom": "mac", 58 | "filename": "app_icon_512.png", 59 | "scale": "1x" 60 | }, 61 | { 62 | "size": "512x512", 63 | "idiom": "mac", 64 | "filename": "app_icon_1024.png", 65 | "scale": "2x" 66 | } 67 | ] 68 | } -------------------------------------------------------------------------------- /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 | # Add preprocessor definitions for the build version. 24 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") 25 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") 26 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") 27 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") 28 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") 29 | 30 | # Disable Windows macros that collide with C++ standard library functions. 31 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 32 | 33 | # Add dependency libraries and include directories. Add any application-specific 34 | # dependencies here. 35 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 36 | target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") 37 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 38 | 39 | # Run the Flutter tool portions of the build. This must not be removed. 40 | add_dependencies(${BINARY_NAME} flutter_assemble) 41 | -------------------------------------------------------------------------------- /windows/packaging/exe/inno-script.iss: -------------------------------------------------------------------------------- 1 | [Setup] 2 | AppId={{APP_ID}} 3 | AppName={{DISPLAY_NAME}} 4 | UninstallDisplayName=JSONTry 5 | UninstallDisplayIcon={app}\{{DISPLAY_NAME}}.exe 6 | AppVersion={{APP_VERSION}} 7 | AppPublisher={{PUBLISHER_NAME}} 8 | AppPublisherURL={{PUBLISHER_URL}} 9 | AppSupportURL={{PUBLISHER_URL}} 10 | AppUpdatesURL={{PUBLISHER_URL}} 11 | DefaultDirName={{INSTALL_DIR_NAME}} 12 | PrivilegesRequired=admin 13 | OutputDir=. 14 | OutputBaseFilename={{OUTPUT_BASE_FILENAME}} 15 | SetupIconFile="{{SETUP_ICON_FILE}}" 16 | Compression=lzma2 17 | SolidCompression=yes 18 | WizardStyle=modern 19 | ArchitecturesInstallIn64BitMode=x64 20 | DisableDirPage=auto 21 | DisableProgramGroupPage=yes 22 | SignTool=CodeSign $f 23 | 24 | [InstallDelete] 25 | Type: filesandordirs; Name: "{app}\*" 26 | 27 | [Languages] 28 | Name: "english"; MessagesFile: "compiler:Default.isl" 29 | 30 | [Tasks] 31 | Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: {% if CREATE_DESKTOP_ICON != true %}unchecked{% else %}checkedonce{% endif %} 32 | Name: "launchAtStartup"; Description: "{cm:AutoStartProgram,{{DISPLAY_NAME}}}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: {% if LAUNCH_AT_STARTUP != true %}unchecked{% else %}checkedonce{% endif %} 33 | 34 | [Files] 35 | Source: "{{SOURCE_DIR}}\\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs 36 | ; NOTE: Don't use "Flags: ignoreversion" on any shared system files 37 | 38 | [Icons] 39 | Name: "{autoprograms}\\{{DISPLAY_NAME}}"; Filename: "{app}\\{{EXECUTABLE_NAME}}" 40 | Name: "{autodesktop}\\{{DISPLAY_NAME}}"; Filename: "{app}\\{{EXECUTABLE_NAME}}"; Tasks: desktopicon 41 | Name: "{userstartup}\\{{DISPLAY_NAME}}"; Filename: "{app}\\{{EXECUTABLE_NAME}}"; WorkingDir: "{app}"; Tasks: launchAtStartup 42 | [Run] 43 | Filename: "{app}\\{{EXECUTABLE_NAME}}"; Description: "{cm:LaunchProgram,{{DISPLAY_NAME}}}"; Flags: {% if PRIVILEGES_REQUIRED == 'admin' %}runascurrentuser{% endif %} nowait postinstall skipifsilent 44 | 45 | -------------------------------------------------------------------------------- /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 | unsigned int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr) 51 | -1; // remove the trailing null character 52 | int input_length = (int)wcslen(utf16_string); 53 | std::string utf8_string; 54 | if (target_length == 0 || target_length > utf8_string.max_size()) { 55 | return utf8_string; 56 | } 57 | utf8_string.resize(target_length); 58 | int converted_length = ::WideCharToMultiByte( 59 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 60 | input_length, utf8_string.data(), target_length, nullptr, nullptr); 61 | if (converted_length == 0) { 62 | return std::string(); 63 | } 64 | return utf8_string; 65 | } 66 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | {"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}} -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. 5 | id "dev.flutter.flutter-gradle-plugin" 6 | } 7 | 8 | def localProperties = new Properties() 9 | def localPropertiesFile = rootProject.file("local.properties") 10 | if (localPropertiesFile.exists()) { 11 | localPropertiesFile.withReader("UTF-8") { reader -> 12 | localProperties.load(reader) 13 | } 14 | } 15 | 16 | def flutterVersionCode = localProperties.getProperty("flutter.versionCode") 17 | if (flutterVersionCode == null) { 18 | flutterVersionCode = "1" 19 | } 20 | 21 | def flutterVersionName = localProperties.getProperty("flutter.versionName") 22 | if (flutterVersionName == null) { 23 | flutterVersionName = "1.0" 24 | } 25 | 26 | android { 27 | namespace = "com.rivafarabi.jsontry" 28 | compileSdk = flutter.compileSdkVersion 29 | ndkVersion = flutter.ndkVersion 30 | 31 | compileOptions { 32 | sourceCompatibility = JavaVersion.VERSION_1_8 33 | targetCompatibility = JavaVersion.VERSION_1_8 34 | } 35 | 36 | defaultConfig { 37 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 38 | applicationId = "com.rivafarabi.jsontry" 39 | // You can update the following values to match your application needs. 40 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. 41 | minSdk = flutter.minSdkVersion 42 | targetSdk = flutter.targetSdkVersion 43 | versionCode = flutterVersionCode.toInteger() 44 | versionName = flutterVersionName 45 | } 46 | 47 | buildTypes { 48 | release { 49 | // TODO: Add your own signing config for the release build. 50 | // Signing with the debug keys for now, so `flutter run --release` works. 51 | signingConfig = signingConfigs.debug 52 | } 53 | } 54 | } 55 | 56 | flutter { 57 | source = "../.." 58 | } 59 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 15 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 33 | 34 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /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 | 30 | flutter_controller_->engine()->SetNextFrameCallback([&]() { 31 | this->Show(); 32 | }); 33 | 34 | // Flutter can complete the first frame before the "show window" callback is 35 | // registered. The following call ensures a frame is pending to ensure the 36 | // window is shown. It is a no-op if the first frame hasn't completed yet. 37 | flutter_controller_->ForceRedraw(); 38 | 39 | return true; 40 | } 41 | 42 | void FlutterWindow::OnDestroy() { 43 | if (flutter_controller_) { 44 | flutter_controller_ = nullptr; 45 | } 46 | 47 | Win32Window::OnDestroy(); 48 | } 49 | 50 | LRESULT 51 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 52 | WPARAM const wparam, 53 | LPARAM const lparam) noexcept { 54 | // Give Flutter, including plugins, an opportunity to handle window messages. 55 | if (flutter_controller_) { 56 | std::optional result = 57 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 58 | lparam); 59 | if (result) { 60 | return *result; 61 | } 62 | } 63 | 64 | switch (message) { 65 | case WM_FONTCHANGE: 66 | flutter_controller_->engine()->ReloadSystemFonts(); 67 | break; 68 | } 69 | 70 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 71 | } 72 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:provider/provider.dart'; 3 | import 'package:universal_platform/universal_platform.dart'; 4 | import 'package:macos_ui/macos_ui.dart'; 5 | import 'package:fluent_ui/fluent_ui.dart' as fluent; 6 | import 'package:window_manager/window_manager.dart'; 7 | import 'providers/json_provider.dart'; 8 | import 'screens/json_viewer_screen.dart'; 9 | 10 | void main() async { 11 | WidgetsFlutterBinding.ensureInitialized(); 12 | await windowManager.ensureInitialized(); 13 | 14 | WindowOptions windowOptions = const WindowOptions( 15 | size: Size(500, 650), 16 | minimumSize: Size(400, 650), 17 | skipTaskbar: false, 18 | title: "JSONTry" 19 | ); 20 | windowManager.waitUntilReadyToShow(windowOptions, () async { 21 | await windowManager.show(); 22 | await windowManager.focus(); 23 | }); 24 | 25 | runApp(const JsonTryApp()); 26 | } 27 | 28 | class JsonTryApp extends StatelessWidget { 29 | const JsonTryApp({super.key}); 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return ChangeNotifierProvider( 34 | create: (context) => JsonProvider(), 35 | child: Consumer( 36 | builder: (context, jsonProvider, child) { 37 | if (UniversalPlatform.isMacOS) { 38 | return MacosApp( 39 | title: jsonProvider.windowTitle, 40 | theme: MacosThemeData.light(), 41 | darkTheme: MacosThemeData.dark(), 42 | home: const JsonViewerScreen(), 43 | debugShowCheckedModeBanner: false, 44 | ); 45 | } else if (UniversalPlatform.isWindows) { 46 | return fluent.FluentApp( 47 | title: jsonProvider.windowTitle, 48 | theme: fluent.FluentThemeData.light(), 49 | darkTheme: fluent.FluentThemeData.dark(), 50 | home: const JsonViewerScreen(), 51 | debugShowCheckedModeBanner: false, 52 | ); 53 | } else { 54 | return MaterialApp( 55 | title: jsonProvider.windowTitle, 56 | theme: ThemeData( 57 | primarySwatch: Colors.blue, 58 | useMaterial3: true, 59 | ), 60 | home: const JsonViewerScreen(), 61 | debugShowCheckedModeBanner: false, 62 | ); 63 | } 64 | }, 65 | ), 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "users": [ 3 | { 4 | "id": 1, 5 | "name": "John Doe", 6 | "email": "john.doe@example.com", 7 | "age": 28, 8 | "isActive": true, 9 | "address": { 10 | "street": "123 Main St", 11 | "city": "New York", 12 | "zipCode": "10001", 13 | "country": "USA" 14 | }, 15 | "hobbies": ["reading", "swimming", "coding"], 16 | "metadata": { 17 | "createdAt": "2023-01-15T10:30:00Z", 18 | "lastLogin": "2023-12-01T14:22:33Z", 19 | "preferences": { 20 | "theme": "dark", 21 | "notifications": true, 22 | "language": "en" 23 | } 24 | } 25 | }, 26 | { 27 | "id": 2, 28 | "name": "Jane Smith", 29 | "email": "jane.smith@example.com", 30 | "age": 32, 31 | "isActive": false, 32 | "address": { 33 | "street": "456 Oak Ave", 34 | "city": "Los Angeles", 35 | "zipCode": "90210", 36 | "country": "USA" 37 | }, 38 | "hobbies": ["photography", "traveling", "cooking"], 39 | "metadata": { 40 | "createdAt": "2023-02-20T09:15:00Z", 41 | "lastLogin": "2023-11-28T16:45:12Z", 42 | "preferences": { 43 | "theme": "light", 44 | "notifications": false, 45 | "language": "es" 46 | } 47 | } 48 | } 49 | ], 50 | "products": [ 51 | { 52 | "id": 101, 53 | "name": "Laptop", 54 | "category": "Electronics", 55 | "price": 999.99, 56 | "inStock": true, 57 | "specifications": { 58 | "processor": "Intel i7", 59 | "memory": "16GB RAM", 60 | "storage": "512GB SSD", 61 | "display": "15.6 inch" 62 | }, 63 | "tags": ["computer", "work", "portable"] 64 | }, 65 | { 66 | "id": 102, 67 | "name": "Coffee Maker", 68 | "category": "Appliances", 69 | "price": 79.99, 70 | "inStock": false, 71 | "specifications": { 72 | "capacity": "12 cups", 73 | "material": "Stainless Steel", 74 | "features": ["programmable", "auto-shutoff"] 75 | }, 76 | "tags": ["kitchen", "coffee", "morning"] 77 | } 78 | ], 79 | "stats": { 80 | "totalUsers": 2, 81 | "activeUsers": 1, 82 | "totalProducts": 2, 83 | "averagePrice": 539.99, 84 | "lastUpdated": "2023-12-01T12:00:00Z" 85 | }, 86 | "config": { 87 | "apiVersion": "v1.2.3", 88 | "environment": "production", 89 | "features": { 90 | "enableLogging": true, 91 | "enableAnalytics": false, 92 | "maxFileSize": 104857600 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /macos/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - appkit_ui_element_colors (1.0.0): 3 | - FlutterMacOS 4 | - desktop_drop (0.0.1): 5 | - FlutterMacOS 6 | - FlutterMacOS (1.0.0) 7 | - macos_ui (0.1.0): 8 | - FlutterMacOS 9 | - macos_window_utils (1.0.0): 10 | - FlutterMacOS 11 | - native_context_menu (0.0.1): 12 | - FlutterMacOS 13 | - screen_retriever_macos (0.0.1): 14 | - FlutterMacOS 15 | - url_launcher_macos (0.0.1): 16 | - FlutterMacOS 17 | - window_manager (0.5.0): 18 | - FlutterMacOS 19 | 20 | DEPENDENCIES: 21 | - appkit_ui_element_colors (from `Flutter/ephemeral/.symlinks/plugins/appkit_ui_element_colors/macos`) 22 | - desktop_drop (from `Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos`) 23 | - FlutterMacOS (from `Flutter/ephemeral`) 24 | - macos_ui (from `Flutter/ephemeral/.symlinks/plugins/macos_ui/macos`) 25 | - macos_window_utils (from `Flutter/ephemeral/.symlinks/plugins/macos_window_utils/macos`) 26 | - native_context_menu (from `Flutter/ephemeral/.symlinks/plugins/native_context_menu/macos`) 27 | - screen_retriever_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos`) 28 | - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) 29 | - window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`) 30 | 31 | EXTERNAL SOURCES: 32 | appkit_ui_element_colors: 33 | :path: Flutter/ephemeral/.symlinks/plugins/appkit_ui_element_colors/macos 34 | desktop_drop: 35 | :path: Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos 36 | FlutterMacOS: 37 | :path: Flutter/ephemeral 38 | macos_ui: 39 | :path: Flutter/ephemeral/.symlinks/plugins/macos_ui/macos 40 | macos_window_utils: 41 | :path: Flutter/ephemeral/.symlinks/plugins/macos_window_utils/macos 42 | native_context_menu: 43 | :path: Flutter/ephemeral/.symlinks/plugins/native_context_menu/macos 44 | screen_retriever_macos: 45 | :path: Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos 46 | url_launcher_macos: 47 | :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos 48 | window_manager: 49 | :path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos 50 | 51 | SPEC CHECKSUMS: 52 | appkit_ui_element_colors: bb247a2d02b8313cc47e1b83be48d996034c4001 53 | desktop_drop: 69eeff437544aa619c8db7f4481b3a65f7696898 54 | FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 55 | macos_ui: 6229a8922cd97bafb7d9636c8eb8dfb0744183ca 56 | macos_window_utils: 721df4da91cb4bde7b2b7b6ae93cdead4851118f 57 | native_context_menu: 8b710e17c5962cf3f005b805eb97cce668b3839a 58 | screen_retriever_macos: 776e0fa5d42c6163d2bf772d22478df4b302b161 59 | url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404 60 | window_manager: e25faf20d88283a0d46e7b1a759d07261ca27575 61 | 62 | PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3 63 | 64 | COCOAPODS: 1.15.2 65 | -------------------------------------------------------------------------------- /lib/widgets/context_menu_button.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:fluent_ui/fluent_ui.dart' as fluent; 6 | import 'package:macos_ui/macos_ui.dart'; 7 | import 'package:native_context_menu/native_context_menu.dart'; 8 | import 'package:universal_platform/universal_platform.dart'; 9 | 10 | ///A custom ContextMenuRegion based on context_menu_region package that support left-click mouse event 11 | class ContextMenuButton extends StatefulWidget { 12 | const ContextMenuButton({ 13 | this.child, 14 | required this.menuItems, 15 | super.key, 16 | this.onItemSelected, 17 | this.onDismissed, 18 | }); 19 | 20 | final Widget? child; 21 | final List menuItems; 22 | final void Function(MenuItem item)? onItemSelected; 23 | final VoidCallback? onDismissed; 24 | 25 | @override 26 | ContextMenuButtonState createState() => ContextMenuButtonState(); 27 | } 28 | 29 | class ContextMenuButtonState extends State { 30 | bool shouldReact = false; 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | late IconData iconData; 35 | late Color iconColor; 36 | 37 | if (UniversalPlatform.isMacOS) { 38 | bool isDark = MacosTheme.of(context).brightness == Brightness.dark; 39 | iconData = CupertinoIcons.ellipsis_vertical; 40 | iconColor = isDark ? CupertinoColors.systemGrey4 : CupertinoColors.black; 41 | } else if (UniversalPlatform.isWindows) { 42 | bool isDark = fluent.FluentTheme.of(context).brightness == Brightness.dark; 43 | iconData = fluent.FluentIcons.more_vertical; 44 | iconColor = isDark ? Colors.grey.shade800 : Colors.black; 45 | } else { 46 | bool isDark = Theme.of(context).brightness == Brightness.dark; 47 | iconData = Icons.more_vert; 48 | iconColor = isDark ? Colors.grey.shade800 : Colors.black; 49 | } 50 | 51 | Widget defaultChild = Container( 52 | width: 20, 53 | height: 20, 54 | color: Colors.transparent, 55 | child: Icon( 56 | iconData, 57 | size: 18, 58 | color: iconColor, 59 | ), 60 | ); 61 | 62 | return Listener( 63 | onPointerDown: (e) { 64 | shouldReact = e.kind == PointerDeviceKind.mouse; 65 | }, 66 | onPointerUp: (e) async { 67 | if (!shouldReact) return; 68 | 69 | shouldReact = false; 70 | 71 | final position = e.position; 72 | 73 | final selectedItem = await showContextMenu( 74 | ShowMenuArgs( 75 | MediaQuery.of(context).devicePixelRatio, 76 | position, 77 | widget.menuItems, 78 | ), 79 | ); 80 | 81 | if (selectedItem != null) { 82 | widget.onItemSelected?.call(selectedItem); 83 | } else { 84 | widget.onDismissed?.call(); 85 | } 86 | }, 87 | child: widget.child ?? defaultChild, 88 | ); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JSONTry Viewer 2 | 3 | JSONTry (pronounced "jay-son-tree") is an experimental JSON viewer application built with Flutter for macOS and Windows. Its primary goal is to benchmark Flutter's performance when handling large JSON data files, making it a valuable tool for developers interested in high-performance desktop applications. JSONTry serves as an alternative to Dadroit JSON Viewer, with some parts of the code "vibe coded" for rapid prototyping and creative solutions. 4 | 5 | image 6 | 7 | ## Features 8 | 9 | ### 🚀 Performance 10 | - **Large File Support**: Optimized for JSON files up to 1GB+ with streaming and compute isolate processing 11 | - **Lazy Loading**: Tree nodes are loaded on-demand for better memory efficiency 12 | - **Search (WIP)**: Real-time search across keys and values with highlighting (work in progress) 13 | 14 | ### 🎨 Modern UI 15 | - **Native Look**: Uses native UI components for macOS and Windows 16 | - **Dark/Light Theme**: Automatic theme detection and support 17 | 18 | ### 🔍 Advanced Features 19 | - **Tree View**: Hierarchical display of JSON structure with expand/collapse 20 | - **Context Menu**: Right-click to copy keys, values, or paths 21 | - **Search**: Filter JSON nodes by key or value 22 | - **Type Indicators**: Visual badges showing data types (Object, Array, String, Number, etc.) 23 | - **Performance Metrics**: Shows file size, load time, and node count 24 | 25 | ## Installation 26 | 27 | ### Prerequisites 28 | - Flutter SDK (>=3.4.0) 29 | - For macOS: Xcode and macOS 10.15+ 30 | - For Windows: Visual Studio with C++ tools 31 | 32 | ### Build Instructions 33 | 34 | 1. Install dependencies: 35 | ```bash 36 | flutter pub get 37 | ``` 38 | 39 | 2. Build for your platform: 40 | 41 | **macOS:** 42 | ```bash 43 | flutter build macos --release 44 | ``` 45 | 46 | **Windows:** 47 | ```bash 48 | flutter build windows --release 49 | ``` 50 | 51 | 3. Run the application (debug): 52 | ```bash 53 | flutter run -d macos # For macOS 54 | flutter run -d windows # For Windows 55 | ``` 56 | 57 | ## Usage 58 | 59 | 1. **Open JSON File**: Click the folder icon in the toolbar or use the "Open JSON File" button 60 | 2. **Navigate**: Click expand/collapse icons to explore the JSON structure 61 | 3. **Search**: Use the search bar to filter nodes by key or value 62 | 4. **Copy Data**: Right-click any node to copy its key, value, or path 63 | 5. **View Status**: Check the status bar for file information and performance metrics 64 | 65 | ## Performance Features 66 | 67 | ### Large File Handling 68 | - Files >50MB are processed using compute isolates to prevent UI blocking 69 | - Streaming approach for very large files 70 | - Memory-efficient tree node structure 71 | 72 | ### Search Optimization 73 | - Real-time filtering with debouncing 74 | - Efficient string matching algorithms 75 | - Maintains tree structure during search 76 | 77 | ### UI Performance 78 | - Virtual scrolling for large datasets 79 | - Lazy loading of tree nodes 80 | - Optimized rendering with Flutter's widget tree 81 | 82 | -------------------------------------------------------------------------------- /linux/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file controls Flutter-level build steps. It should not be edited. 2 | cmake_minimum_required(VERSION 3.10) 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 | 12 | # Serves the same purpose as list(TRANSFORM ... PREPEND ...), 13 | # which isn't available in 3.10. 14 | function(list_prepend LIST_NAME PREFIX) 15 | set(NEW_LIST "") 16 | foreach(element ${${LIST_NAME}}) 17 | list(APPEND NEW_LIST "${PREFIX}${element}") 18 | endforeach(element) 19 | set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) 20 | endfunction() 21 | 22 | # === Flutter Library === 23 | # System-level dependencies. 24 | find_package(PkgConfig REQUIRED) 25 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 26 | pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) 27 | pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) 28 | 29 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") 30 | 31 | # Published to parent scope for install step. 32 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 33 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 34 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 35 | set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) 36 | 37 | list(APPEND FLUTTER_LIBRARY_HEADERS 38 | "fl_basic_message_channel.h" 39 | "fl_binary_codec.h" 40 | "fl_binary_messenger.h" 41 | "fl_dart_project.h" 42 | "fl_engine.h" 43 | "fl_json_message_codec.h" 44 | "fl_json_method_codec.h" 45 | "fl_message_codec.h" 46 | "fl_method_call.h" 47 | "fl_method_channel.h" 48 | "fl_method_codec.h" 49 | "fl_method_response.h" 50 | "fl_plugin_registrar.h" 51 | "fl_plugin_registry.h" 52 | "fl_standard_message_codec.h" 53 | "fl_standard_method_codec.h" 54 | "fl_string_codec.h" 55 | "fl_value.h" 56 | "fl_view.h" 57 | "flutter_linux.h" 58 | ) 59 | list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") 60 | add_library(flutter INTERFACE) 61 | target_include_directories(flutter INTERFACE 62 | "${EPHEMERAL_DIR}" 63 | ) 64 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") 65 | target_link_libraries(flutter INTERFACE 66 | PkgConfig::GTK 67 | PkgConfig::GLIB 68 | PkgConfig::GIO 69 | ) 70 | add_dependencies(flutter flutter_assemble) 71 | 72 | # === Flutter tool backend === 73 | # _phony_ is a non-existent file to force this command to run every time, 74 | # since currently there's no way to get a full input/output list from the 75 | # flutter tool. 76 | add_custom_command( 77 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 78 | ${CMAKE_CURRENT_BINARY_DIR}/_phony_ 79 | COMMAND ${CMAKE_COMMAND} -E env 80 | ${FLUTTER_TOOL_ENVIRONMENT} 81 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" 82 | ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} 83 | VERBATIM 84 | ) 85 | add_custom_target(flutter_assemble DEPENDS 86 | "${FLUTTER_LIBRARY}" 87 | ${FLUTTER_LIBRARY_HEADERS} 88 | ) 89 | -------------------------------------------------------------------------------- /lib/utils/app_color_scheme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:fluent_ui/fluent_ui.dart' as fluent; 3 | import 'package:jsontry/models/json_node.dart'; 4 | import 'package:macos_ui/macos_ui.dart'; 5 | import 'package:universal_platform/universal_platform.dart'; 6 | 7 | class AppColorScheme { 8 | late Color backgroundColor; 9 | late Color keyColor; 10 | late Color dividerColor; 11 | late Color evenRowColor; 12 | late Color oddRowColor; 13 | late Color selectedColor; 14 | late Color searchMatchColor; 15 | late Color currentResultColor; 16 | late Color expansionButtonColor; 17 | late bool isDark; 18 | 19 | // Cached type colors 20 | static const Map _typeColors = { 21 | JsonNodeType.object: Color(0xFF2196F3), 22 | JsonNodeType.array: Color(0xFF3F51B5), 23 | JsonNodeType.string: Color(0xFF4CAF50), 24 | JsonNodeType.number: Color(0xFFFF9800), 25 | JsonNodeType.boolean: Color(0xFF9C27B0), 26 | JsonNodeType.nullValue: Color(0xFF757575), 27 | }; 28 | 29 | void updateColors(BuildContext context) { 30 | if (UniversalPlatform.isMacOS) { 31 | isDark = MacosTheme.of(context).brightness == Brightness.dark; 32 | backgroundColor = MacosTheme.of(context).canvasColor; 33 | dividerColor = MacosTheme.of(context).dividerColor; 34 | } else if (UniversalPlatform.isWindows) { 35 | isDark = fluent.FluentTheme.of(context).brightness == Brightness.dark; 36 | final fluentTheme = fluent.FluentTheme.maybeOf(context); 37 | backgroundColor = fluentTheme?.scaffoldBackgroundColor ?? Theme.of(context).scaffoldBackgroundColor; 38 | dividerColor = fluentTheme?.resources.dividerStrokeColorDefault ?? Theme.of(context).dividerColor; 39 | } else { 40 | isDark = Theme.of(context).brightness == Brightness.dark; 41 | backgroundColor = Theme.of(context).scaffoldBackgroundColor; 42 | dividerColor = Theme.of(context).dividerColor; 43 | } 44 | 45 | keyColor = isDark ? Colors.blue.shade300 : Colors.blue.shade700; 46 | selectedColor = isDark ? Colors.blue.shade400 : Colors.blue.shade800; 47 | evenRowColor = isDark ? Colors.grey.shade800.withOpacity(0.3) : Colors.grey.shade50; 48 | oddRowColor = isDark ? Colors.grey.shade900.withOpacity(0.2) : Colors.white; 49 | 50 | searchMatchColor = isDark ? Colors.blue.shade600.withOpacity(0.3) : Colors.blue.shade200.withOpacity(0.3); 51 | currentResultColor = isDark ? Colors.blue.shade600.withOpacity(0.8) : Colors.blue.shade200.withOpacity(0.8); 52 | expansionButtonColor = isDark ? Colors.white : Colors.grey.shade400; 53 | } 54 | 55 | Color getTypeColor(JsonNodeType type) => _typeColors[type]!; 56 | 57 | Color getValueColor(JsonNodeType type) { 58 | switch (type) { 59 | case JsonNodeType.string: 60 | return isDark ? Colors.green.shade300 : Colors.green.shade700; 61 | case JsonNodeType.number: 62 | return isDark ? Colors.orange.shade300 : Colors.orange.shade700; 63 | case JsonNodeType.boolean: 64 | return isDark ? Colors.purple.shade300 : Colors.purple.shade700; 65 | case JsonNodeType.nullValue: 66 | return isDark ? Colors.grey.shade400 : Colors.grey.shade600; 67 | default: 68 | return isDark ? Colors.blue.shade300 : Colors.blue.shade700; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /test/json_provider_optimization_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:jsontry/models/json_node.dart'; 3 | import 'package:jsontry/providers/json_provider.dart'; 4 | 5 | void main() { 6 | group('JsonProvider Optimization Tests', () { 7 | late JsonProvider jsonProvider; 8 | 9 | setUp(() { 10 | jsonProvider = JsonProvider(); 11 | }); 12 | 13 | tearDown(() { 14 | jsonProvider.dispose(); 15 | }); 16 | 17 | test('collectParentPaths should collect unique parent paths', () { 18 | final pathsToExpand = {}; 19 | 20 | // Test with simple nested path 21 | jsonProvider.collectParentPaths('a.b.c', pathsToExpand); 22 | expect(pathsToExpand, contains('a')); 23 | expect(pathsToExpand, contains('a.b')); 24 | expect(pathsToExpand, hasLength(2)); 25 | 26 | // Test with array notation 27 | pathsToExpand.clear(); 28 | jsonProvider.collectParentPaths('users[0].name', pathsToExpand); 29 | expect(pathsToExpand, contains('users')); 30 | expect(pathsToExpand, contains('users[0]')); 31 | expect(pathsToExpand, hasLength(2)); 32 | }); 33 | 34 | test('collectParentPaths should deduplicate paths', () { 35 | final pathsToExpand = {}; 36 | 37 | // Add multiple paths with shared parents 38 | jsonProvider.collectParentPaths('a.b.c', pathsToExpand); 39 | jsonProvider.collectParentPaths('a.b.d', pathsToExpand); 40 | jsonProvider.collectParentPaths('a.e.f', pathsToExpand); 41 | 42 | // Should only have unique parent paths 43 | expect(pathsToExpand, contains('a')); 44 | expect(pathsToExpand, contains('a.b')); 45 | expect(pathsToExpand, contains('a.e')); 46 | expect(pathsToExpand, hasLength(3)); // a, a.b, a.e 47 | }); 48 | 49 | test('expandPathsBatch should efficiently expand multiple paths', () { 50 | // Create a simple node structure 51 | final grandchildNode = JsonNode( 52 | key: 'grandchild', 53 | value: 'value', 54 | type: JsonNodeType.string, 55 | depth: 3, 56 | path: 'root.parent.grandchild', 57 | ); 58 | 59 | final childNode = JsonNode( 60 | key: 'child', 61 | value: {'grandchild': 'value'}, 62 | type: JsonNodeType.object, 63 | depth: 2, 64 | path: 'root.parent', 65 | isExpanded: false, 66 | children: [grandchildNode], 67 | ); 68 | 69 | final parentNode = JsonNode( 70 | key: 'parent', 71 | value: {'child': {'grandchild': 'value'}}, 72 | type: JsonNodeType.object, 73 | depth: 1, 74 | path: 'root', 75 | isExpanded: false, 76 | children: [childNode], 77 | ); 78 | 79 | final nodes = [parentNode]; 80 | final pathsToExpand = {'root', 'root.parent'}; 81 | 82 | final result = jsonProvider.expandPathsBatch(nodes, pathsToExpand); 83 | 84 | // Root should be expanded 85 | expect(result.first.isExpanded, isTrue); 86 | 87 | // Child (which has path root.parent) should be expanded 88 | final expandedChild = result.first.children!.first; 89 | expect(expandedChild.isExpanded, isTrue); 90 | }); 91 | 92 | test('performance tracking fields exist', () { 93 | // Just verify the performance tracking fields are accessible 94 | expect(jsonProvider.expandedPathsCount, equals(0)); 95 | expect(jsonProvider.lastExpansionDuration, isNull); 96 | }); 97 | }); 98 | } -------------------------------------------------------------------------------- /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 | #if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) 64 | #define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD 65 | #else 66 | #define VERSION_AS_NUMBER 1,0,0,0 67 | #endif 68 | 69 | #if defined(FLUTTER_VERSION) 70 | #define VERSION_AS_STRING FLUTTER_VERSION 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.rivafarabi.jsontry" "\0" 93 | VALUE "FileDescription", "JSONTry" "\0" 94 | VALUE "FileVersion", VERSION_AS_STRING "\0" 95 | VALUE "InternalName", "JSONTry" "\0" 96 | VALUE "LegalCopyright", "Copyright (C) 2025 Riva Farabi (Bigvaria). All rights reserved." "\0" 97 | VALUE "OriginalFilename", "jsontry.exe" "\0" 98 | VALUE "ProductName", "JSONTry" "\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 | -------------------------------------------------------------------------------- /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 a win32 window with |title| that is positioned and sized 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 this function will scale the inputted width and height as 35 | // as appropriate for the default monitor. The window is invisible until 36 | // |Show| is called. Returns true if the window was created successfully. 37 | bool Create(const std::wstring& title, const Point& origin, const Size& size); 38 | 39 | // Show the current window. Returns true if the window was successfully shown. 40 | bool Show(); 41 | 42 | // Release OS resources associated with window. 43 | void Destroy(); 44 | 45 | // Inserts |content| into the window tree. 46 | void SetChildContent(HWND content); 47 | 48 | // Returns the backing Window handle to enable clients to set icon and other 49 | // window properties. Returns nullptr if the window has been destroyed. 50 | HWND GetHandle(); 51 | 52 | // If true, closing this window will quit the application. 53 | void SetQuitOnClose(bool quit_on_close); 54 | 55 | // Return a RECT representing the bounds of the current client area. 56 | RECT GetClientArea(); 57 | 58 | protected: 59 | // Processes and route salient window messages for mouse handling, 60 | // size change and DPI. Delegates handling of these to member overloads that 61 | // inheriting classes can handle. 62 | virtual LRESULT MessageHandler(HWND window, 63 | UINT const message, 64 | WPARAM const wparam, 65 | LPARAM const lparam) noexcept; 66 | 67 | // Called when CreateAndShow is called, allowing subclass window-related 68 | // setup. Subclasses should return false if setup fails. 69 | virtual bool OnCreate(); 70 | 71 | // Called when Destroy is called. 72 | virtual void OnDestroy(); 73 | 74 | private: 75 | friend class WindowClassRegistrar; 76 | 77 | // OS callback called by message pump. Handles the WM_NCCREATE message which 78 | // is passed when the non-client area is being created and enables automatic 79 | // non-client DPI scaling so that the non-client area automatically 80 | // responds to changes in DPI. All other messages are handled by 81 | // MessageHandler. 82 | static LRESULT CALLBACK WndProc(HWND const window, 83 | UINT const message, 84 | WPARAM const wparam, 85 | LPARAM const lparam) noexcept; 86 | 87 | // Retrieves a class instance pointer for |window| 88 | static Win32Window* GetThisFromHandle(HWND const window) noexcept; 89 | 90 | // Update the window frame's theme to match the system theme. 91 | static void UpdateTheme(HWND const window); 92 | 93 | bool quit_on_close_ = false; 94 | 95 | // window handle for top level window. 96 | HWND window_handle_ = nullptr; 97 | 98 | // window handle for hosted content. 99 | HWND child_content_ = nullptr; 100 | }; 101 | 102 | #endif // RUNNER_WIN32_WINDOW_H_ 103 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 65 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 65 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /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 | # Set fallback configurations for older versions of the flutter tool. 14 | if (NOT DEFINED FLUTTER_TARGET_PLATFORM) 15 | set(FLUTTER_TARGET_PLATFORM "windows-x64") 16 | endif() 17 | 18 | # === Flutter Library === 19 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") 20 | 21 | # Published to parent scope for install step. 22 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 23 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 24 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 25 | set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) 26 | 27 | list(APPEND FLUTTER_LIBRARY_HEADERS 28 | "flutter_export.h" 29 | "flutter_windows.h" 30 | "flutter_messenger.h" 31 | "flutter_plugin_registrar.h" 32 | "flutter_texture_registrar.h" 33 | ) 34 | list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") 35 | add_library(flutter INTERFACE) 36 | target_include_directories(flutter INTERFACE 37 | "${EPHEMERAL_DIR}" 38 | ) 39 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") 40 | add_dependencies(flutter flutter_assemble) 41 | 42 | # === Wrapper === 43 | list(APPEND CPP_WRAPPER_SOURCES_CORE 44 | "core_implementations.cc" 45 | "standard_codec.cc" 46 | ) 47 | list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") 48 | list(APPEND CPP_WRAPPER_SOURCES_PLUGIN 49 | "plugin_registrar.cc" 50 | ) 51 | list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") 52 | list(APPEND CPP_WRAPPER_SOURCES_APP 53 | "flutter_engine.cc" 54 | "flutter_view_controller.cc" 55 | ) 56 | list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") 57 | 58 | # Wrapper sources needed for a plugin. 59 | add_library(flutter_wrapper_plugin STATIC 60 | ${CPP_WRAPPER_SOURCES_CORE} 61 | ${CPP_WRAPPER_SOURCES_PLUGIN} 62 | ) 63 | apply_standard_settings(flutter_wrapper_plugin) 64 | set_target_properties(flutter_wrapper_plugin PROPERTIES 65 | POSITION_INDEPENDENT_CODE ON) 66 | set_target_properties(flutter_wrapper_plugin PROPERTIES 67 | CXX_VISIBILITY_PRESET hidden) 68 | target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) 69 | target_include_directories(flutter_wrapper_plugin PUBLIC 70 | "${WRAPPER_ROOT}/include" 71 | ) 72 | add_dependencies(flutter_wrapper_plugin flutter_assemble) 73 | 74 | # Wrapper sources needed for the runner. 75 | add_library(flutter_wrapper_app STATIC 76 | ${CPP_WRAPPER_SOURCES_CORE} 77 | ${CPP_WRAPPER_SOURCES_APP} 78 | ) 79 | apply_standard_settings(flutter_wrapper_app) 80 | target_link_libraries(flutter_wrapper_app PUBLIC flutter) 81 | target_include_directories(flutter_wrapper_app PUBLIC 82 | "${WRAPPER_ROOT}/include" 83 | ) 84 | add_dependencies(flutter_wrapper_app flutter_assemble) 85 | 86 | # === Flutter tool backend === 87 | # _phony_ is a non-existent file to force this command to run every time, 88 | # since currently there's no way to get a full input/output list from the 89 | # flutter tool. 90 | set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") 91 | set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) 92 | add_custom_command( 93 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 94 | ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} 95 | ${CPP_WRAPPER_SOURCES_APP} 96 | ${PHONY_OUTPUT} 97 | COMMAND ${CMAKE_COMMAND} -E env 98 | ${FLUTTER_TOOL_ENVIRONMENT} 99 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" 100 | ${FLUTTER_TARGET_PLATFORM} $ 101 | VERBATIM 102 | ) 103 | add_custom_target(flutter_assemble DEPENDS 104 | "${FLUTTER_LIBRARY}" 105 | ${FLUTTER_LIBRARY_HEADERS} 106 | ${CPP_WRAPPER_SOURCES_CORE} 107 | ${CPP_WRAPPER_SOURCES_PLUGIN} 108 | ${CPP_WRAPPER_SOURCES_APP} 109 | ) 110 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: jsontry 2 | description: "A new Flutter project." 3 | # The following line prevents the package from being accidentally published to 4 | # pub.dev using `flutter pub publish`. This is preferred for private packages. 5 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 6 | 7 | # The following defines the version and build number for your application. 8 | # A version number is three numbers separated by dots, like 1.2.43 9 | # followed by an optional build number separated by a +. 10 | # Both the version and the builder number may be overridden in flutter 11 | # build by specifying --build-name and --build-number, respectively. 12 | # In Android, build-name is used as versionName while build-number used as versionCode. 13 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 14 | # In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. 15 | # Read more about iOS versioning at 16 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 17 | # In Windows, build-name is used as the major, minor, and patch parts 18 | # of the product and file versions while build-number is used as the build suffix. 19 | version: 1.0.1+2 20 | 21 | environment: 22 | sdk: '>=3.4.0 <4.0.0' 23 | 24 | # Dependencies specify other packages that your package needs in order to work. 25 | # To automatically upgrade your package dependencies to the latest versions 26 | # consider running `flutter pub upgrade --major-versions`. Alternatively, 27 | # dependencies can be manually updated by changing the version numbers below to 28 | # the latest version available on pub.dev. To see which dependencies have newer 29 | # versions available, run `flutter pub outdated`. 30 | dependencies: 31 | flutter: 32 | sdk: flutter 33 | 34 | context_menus: ^1.0.2 35 | cupertino_icons: ^1.0.6 36 | desktop_drop: ^0.4.4 37 | file_picker: ^6.1.1 38 | fluent_ui: ^4.8.6 39 | macos_ui: ^2.0.4 40 | menu_bar: ^0.5.3 41 | native_context_menu: ^0.2.2+5 42 | path: ^1.8.3 43 | provider: ^6.1.1 44 | stream_transform: ^2.1.0 45 | universal_platform: ^1.0.0+1 46 | window_manager: ^0.5.1 47 | url_launcher: ^6.3.2 48 | package_info_plus: ^9.0.0 49 | 50 | dev_dependencies: 51 | flutter_test: 52 | sdk: flutter 53 | 54 | # The "flutter_lints" package below contains a set of recommended lints to 55 | # encourage good coding practices. The lint set provided by the package is 56 | # activated in the `analysis_options.yaml` file located at the root of your 57 | # package. See that file for information about deactivating specific lint 58 | # rules and activating additional ones. 59 | flutter_lints: ^3.0.0 60 | inno_bundle: ^0.11.2 61 | flutter_launcher_icons: ^0.14.4 62 | 63 | # For information on the generic Dart part of this file, see the 64 | # following page: https://dart.dev/tools/pub/pubspec 65 | 66 | # The following section is specific to Flutter packages. 67 | flutter: 68 | 69 | # The following line ensures that the Material Icons font is 70 | # included with your application, so that you can use the icons in 71 | # the material Icons class. 72 | uses-material-design: true 73 | 74 | # To add assets to your application, add an assets section, like this: 75 | assets: 76 | - assets/icons/ 77 | # - images/a_dot_burr.jpeg 78 | # - images/a_dot_ham.jpeg 79 | 80 | # An image asset can refer to one or more resolution-specific "variants", see 81 | # https://flutter.dev/assets-and-images/#resolution-aware 82 | 83 | # For details regarding adding assets from package dependencies, see 84 | # https://flutter.dev/assets-and-images/#from-packages 85 | 86 | # To add custom fonts to your application, add a fonts section here, 87 | # in this "flutter" section. Each entry in this list should have a 88 | # "family" key with the font family name, and a "fonts" key with a 89 | # list giving the asset and other descriptors for the font. For 90 | # example: 91 | # fonts: 92 | # - family: Schyler 93 | # fonts: 94 | # - asset: fonts/Schyler-Regular.ttf 95 | # - asset: fonts/Schyler-Italic.ttf 96 | # style: italic 97 | # - family: Trajan Pro 98 | # fonts: 99 | # - asset: fonts/TrajanPro.ttf 100 | # - asset: fonts/TrajanPro_Bold.ttf 101 | # weight: 700 102 | # 103 | # For details regarding fonts from package dependencies, 104 | # see https://flutter.dev/custom-fonts/#from-packages 105 | -------------------------------------------------------------------------------- /windows/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Project-level configuration. 2 | cmake_minimum_required(VERSION 3.14) 3 | project(jsontry 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 "jsontry") 8 | 9 | # Explicitly opt in to modern CMake behaviors to avoid warnings with recent 10 | # versions of CMake. 11 | cmake_policy(VERSION 3.14...3.25) 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 | 56 | # Generated plugin build rules, which manage building the plugins and adding 57 | # them to the application. 58 | include(flutter/generated_plugins.cmake) 59 | 60 | 61 | # === Installation === 62 | # Support files are copied into place next to the executable, so that it can 63 | # run in place. This is done instead of making a separate bundle (as on Linux) 64 | # so that building and running from within Visual Studio will work. 65 | set(BUILD_BUNDLE_DIR "$") 66 | # Make the "install" step default, as it's required to run. 67 | set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) 68 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 69 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 70 | endif() 71 | 72 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 73 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") 74 | 75 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 76 | COMPONENT Runtime) 77 | 78 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 79 | COMPONENT Runtime) 80 | 81 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 82 | COMPONENT Runtime) 83 | 84 | if(PLUGIN_BUNDLED_LIBRARIES) 85 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" 86 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 87 | COMPONENT Runtime) 88 | endif() 89 | 90 | # Copy the native assets provided by the build.dart from all packages. 91 | set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") 92 | install(DIRECTORY "${NATIVE_ASSETS_DIR}" 93 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 94 | COMPONENT Runtime) 95 | 96 | # Fully re-copy the assets directory on each build to avoid having stale files 97 | # from a previous install. 98 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 99 | install(CODE " 100 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 101 | " COMPONENT Runtime) 102 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 103 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 104 | 105 | # Install the AOT library on non-Debug builds only. 106 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 107 | CONFIGURATIONS Profile;Release 108 | COMPONENT Runtime) 109 | -------------------------------------------------------------------------------- /lib/widgets/json_tree_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:jsontry/models/json_node.dart'; 3 | import 'package:jsontry/utils/app_color_scheme.dart'; 4 | import 'package:jsontry/utils/style_cache.dart'; 5 | import 'package:jsontry/widgets/json_node_row.dart'; 6 | import 'package:provider/provider.dart'; 7 | import 'package:jsontry/providers/json_provider.dart'; 8 | 9 | class JsonTreeView extends StatefulWidget { 10 | const JsonTreeView({super.key}); 11 | 12 | @override 13 | State createState() => _JsonTreeViewState(); 14 | } 15 | 16 | class _JsonTreeViewState extends State { 17 | String? _lastSearchQuery; 18 | 19 | late final AppColorScheme _colorScheme; 20 | late final StyleCache _styleCache; 21 | 22 | @override 23 | void initState() { 24 | super.initState(); 25 | _colorScheme = AppColorScheme(); 26 | _styleCache = StyleCache(); 27 | } 28 | 29 | @override 30 | void didChangeDependencies() { 31 | super.didChangeDependencies(); 32 | // Update color scheme when theme changes 33 | _colorScheme.updateColors(context); 34 | _styleCache.updateStyles(); 35 | } 36 | 37 | @override 38 | void dispose() { 39 | context.read().scrollController.dispose(); 40 | super.dispose(); 41 | } 42 | 43 | @override 44 | Widget build(BuildContext context) { 45 | return Container( 46 | color: _colorScheme.backgroundColor, 47 | child: Consumer( 48 | builder: (context, provider, child) { 49 | // Clear state when search is cleared 50 | if (provider.searchQuery.isEmpty) { 51 | _lastSearchQuery = null; 52 | } 53 | 54 | // Check if search query changed (new search performed) 55 | if (provider.searchQuery != _lastSearchQuery) { 56 | _lastSearchQuery = provider.searchQuery; 57 | } 58 | 59 | // Use optimized ListView for large datasets 60 | return _buildOptimizedListView(provider); 61 | }, 62 | ), 63 | ); 64 | } 65 | 66 | Widget _buildOptimizedListView(JsonProvider provider) { 67 | final itemCount = provider.nodes.length; 68 | 69 | // For very large lists, use additional optimizations 70 | if (itemCount > 1000) { 71 | return ListView.builder( 72 | controller: provider.scrollController, 73 | itemCount: itemCount, 74 | itemBuilder: (context, index) { 75 | return JsonNodeRow( 76 | node: provider.nodes[index], 77 | provider: provider, 78 | index: index, 79 | colorScheme: _colorScheme, 80 | styleCache: _styleCache, 81 | isSelected: provider.nodes[index].isSelected, 82 | onTap: () => _handleNodeTap(provider.nodes[index], provider), 83 | onToggleTap: () => _handleToggleTap(provider.nodes[index], provider), 84 | onContextMenu: provider.handleContextMenuAction, 85 | ); 86 | }, 87 | addAutomaticKeepAlives: false, 88 | addRepaintBoundaries: true, 89 | addSemanticIndexes: false, 90 | cacheExtent: 500, // Increased cache for better scrolling 91 | physics: const ClampingScrollPhysics(), // Better for large lists 92 | itemExtent: 25, // Fixed height for better performance 93 | ); 94 | } else { 95 | // Use standard ListView for smaller lists 96 | return ListView.builder( 97 | controller: provider.scrollController, 98 | itemCount: itemCount, 99 | itemBuilder: (context, index) { 100 | return JsonNodeRow( 101 | node: provider.nodes[index], 102 | provider: provider, 103 | index: index, 104 | colorScheme: _colorScheme, 105 | styleCache: _styleCache, 106 | isSelected: provider.nodes[index].isSelected, 107 | onTap: () => _handleNodeTap(provider.nodes[index], provider), 108 | onToggleTap: () => _handleToggleTap(provider.nodes[index], provider), 109 | onContextMenu: provider.handleContextMenuAction, 110 | ); 111 | }, 112 | addAutomaticKeepAlives: false, 113 | addRepaintBoundaries: true, 114 | addSemanticIndexes: false, 115 | cacheExtent: 100, 116 | ); 117 | } 118 | } 119 | 120 | void _handleNodeTap(JsonNode node, JsonProvider provider) { 121 | provider.selectNode(node); 122 | } 123 | 124 | void _handleToggleTap(JsonNode node, JsonProvider provider) { 125 | if (node.isCollapsible) { 126 | provider.toggleNode(node.path); 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /lib/widgets/macos_platform_menu.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:jsontry/models/json_node.dart'; 4 | import 'package:jsontry/providers/json_provider.dart'; 5 | import 'package:macos_ui/macos_ui.dart'; 6 | import 'package:provider/provider.dart'; 7 | 8 | class MacosPlatformMenu extends StatefulWidget { 9 | final Widget child; 10 | 11 | const MacosPlatformMenu({super.key, required this.child}); 12 | 13 | @override 14 | State createState() => _MacosPlatformMenuState(); 15 | } 16 | 17 | class _MacosPlatformMenuState extends State { 18 | @override 19 | Widget build(BuildContext context) { 20 | return Selector( 21 | selector: (_, provider) => provider.selectedNode != null, 22 | builder: (_, nodeSelected, ___) => PlatformMenuBar( 23 | menus: [ 24 | PlatformMenu( 25 | label: 'App Menu', 26 | menus: [ 27 | const PlatformMenuItemGroup( 28 | members: [ 29 | PlatformProvidedMenuItem(type: PlatformProvidedMenuItemType.about), 30 | ], 31 | ), 32 | const PlatformMenuItemGroup( 33 | members: [ 34 | PlatformProvidedMenuItem(type: PlatformProvidedMenuItemType.hide), 35 | ], 36 | ), 37 | if (PlatformProvidedMenuItem.hasMenu(PlatformProvidedMenuItemType.quit)) 38 | const PlatformProvidedMenuItem(type: PlatformProvidedMenuItemType.quit), 39 | ], 40 | ), 41 | PlatformMenu( 42 | label: 'File', 43 | menus: [ 44 | PlatformMenuItemGroup( 45 | members: [ 46 | PlatformMenuItem( 47 | label: 'Open...', 48 | shortcut: const SingleActivator(LogicalKeyboardKey.keyO, meta: true), 49 | onSelected: context.read().loadJsonFile, 50 | ), 51 | PlatformMenuItem( 52 | label: 'Open from Clipboard...', 53 | shortcut: const SingleActivator(LogicalKeyboardKey.keyV, meta: true, shift: true), 54 | onSelected: () => _openFromClipboard(context), 55 | ), 56 | ], 57 | ), 58 | ], 59 | ), 60 | PlatformMenu( 61 | label: 'Edit', 62 | menus: [ 63 | PlatformMenuItemGroup( 64 | members: [ 65 | PlatformMenuItem( 66 | label: 'Copy Selected Key', 67 | onSelected: nodeSelected ? () => _handleEditContextMenu("Copy Key") : null, 68 | ), 69 | PlatformMenuItem( 70 | label: 'Copy Selected Value', 71 | onSelected: nodeSelected ? () => _handleEditContextMenu("Copy Value") : null, 72 | ), 73 | PlatformMenuItem( 74 | label: 'Copy Selected Path', 75 | onSelected: nodeSelected ? () => _handleEditContextMenu("Copy Path") : null, 76 | ), 77 | ], 78 | ), 79 | ], 80 | ), 81 | ], 82 | child: widget.child, 83 | ), 84 | ); 85 | } 86 | 87 | void _handleEditContextMenu(String action) { 88 | JsonNode? node = context.read().selectedNode; 89 | 90 | if (node == null) return; 91 | 92 | context.read().handleContextMenuAction(action, node); 93 | } 94 | 95 | Future _openFromClipboard(BuildContext context) async { 96 | try { 97 | final clipboardData = await Clipboard.getData('text/plain'); 98 | 99 | if (!context.mounted) return; 100 | 101 | if (clipboardData?.text != null && clipboardData!.text!.isNotEmpty) { 102 | await context.read().loadJsonFromString(clipboardData.text!); 103 | } else { 104 | _showErrorDialog(context, 'Clipboard is empty or does not contain text.'); 105 | } 106 | } catch (e) { 107 | if (context.mounted) { 108 | _showErrorDialog(context, 'Failed to read from clipboard: $e'); 109 | } 110 | } 111 | } 112 | 113 | void _showErrorDialog(BuildContext context, String message) { 114 | showMacosAlertDialog( 115 | context: context, 116 | builder: (context) => MacosAlertDialog( 117 | appIcon: const MacosIcon(CupertinoIcons.exclamationmark_triangle), 118 | title: const Text('Error'), 119 | message: Text(message), 120 | primaryButton: PushButton( 121 | controlSize: ControlSize.large, 122 | child: const Text('OK'), 123 | onPressed: () => Navigator.of(context).pop(), 124 | ), 125 | ), 126 | ); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /linux/my_application.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | #include 4 | #ifdef GDK_WINDOWING_X11 5 | #include 6 | #endif 7 | 8 | #include "flutter/generated_plugin_registrant.h" 9 | 10 | struct _MyApplication { 11 | GtkApplication parent_instance; 12 | char** dart_entrypoint_arguments; 13 | }; 14 | 15 | G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) 16 | 17 | // Implements GApplication::activate. 18 | static void my_application_activate(GApplication* application) { 19 | MyApplication* self = MY_APPLICATION(application); 20 | GtkWindow* window = 21 | GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); 22 | 23 | // Use a header bar when running in GNOME as this is the common style used 24 | // by applications and is the setup most users will be using (e.g. Ubuntu 25 | // desktop). 26 | // If running on X and not using GNOME then just use a traditional title bar 27 | // in case the window manager does more exotic layout, e.g. tiling. 28 | // If running on Wayland assume the header bar will work (may need changing 29 | // if future cases occur). 30 | gboolean use_header_bar = TRUE; 31 | #ifdef GDK_WINDOWING_X11 32 | GdkScreen* screen = gtk_window_get_screen(window); 33 | if (GDK_IS_X11_SCREEN(screen)) { 34 | const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); 35 | if (g_strcmp0(wm_name, "GNOME Shell") != 0) { 36 | use_header_bar = FALSE; 37 | } 38 | } 39 | #endif 40 | if (use_header_bar) { 41 | GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); 42 | gtk_widget_show(GTK_WIDGET(header_bar)); 43 | gtk_header_bar_set_title(header_bar, "jsontry"); 44 | gtk_header_bar_set_show_close_button(header_bar, TRUE); 45 | gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); 46 | } else { 47 | gtk_window_set_title(window, "jsontry"); 48 | } 49 | 50 | gtk_window_set_default_size(window, 1280, 720); 51 | gtk_widget_show(GTK_WIDGET(window)); 52 | 53 | g_autoptr(FlDartProject) project = fl_dart_project_new(); 54 | fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); 55 | 56 | FlView* view = fl_view_new(project); 57 | gtk_widget_show(GTK_WIDGET(view)); 58 | gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); 59 | 60 | fl_register_plugins(FL_PLUGIN_REGISTRY(view)); 61 | 62 | gtk_widget_grab_focus(GTK_WIDGET(view)); 63 | } 64 | 65 | // Implements GApplication::local_command_line. 66 | static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { 67 | MyApplication* self = MY_APPLICATION(application); 68 | // Strip out the first argument as it is the binary name. 69 | self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); 70 | 71 | g_autoptr(GError) error = nullptr; 72 | if (!g_application_register(application, nullptr, &error)) { 73 | g_warning("Failed to register: %s", error->message); 74 | *exit_status = 1; 75 | return TRUE; 76 | } 77 | 78 | g_application_activate(application); 79 | *exit_status = 0; 80 | 81 | return TRUE; 82 | } 83 | 84 | // Implements GApplication::startup. 85 | static void my_application_startup(GApplication* application) { 86 | //MyApplication* self = MY_APPLICATION(object); 87 | 88 | // Perform any actions required at application startup. 89 | 90 | G_APPLICATION_CLASS(my_application_parent_class)->startup(application); 91 | } 92 | 93 | // Implements GApplication::shutdown. 94 | static void my_application_shutdown(GApplication* application) { 95 | //MyApplication* self = MY_APPLICATION(object); 96 | 97 | // Perform any actions required at application shutdown. 98 | 99 | G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); 100 | } 101 | 102 | // Implements GObject::dispose. 103 | static void my_application_dispose(GObject* object) { 104 | MyApplication* self = MY_APPLICATION(object); 105 | g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); 106 | G_OBJECT_CLASS(my_application_parent_class)->dispose(object); 107 | } 108 | 109 | static void my_application_class_init(MyApplicationClass* klass) { 110 | G_APPLICATION_CLASS(klass)->activate = my_application_activate; 111 | G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; 112 | G_APPLICATION_CLASS(klass)->startup = my_application_startup; 113 | G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; 114 | G_OBJECT_CLASS(klass)->dispose = my_application_dispose; 115 | } 116 | 117 | static void my_application_init(MyApplication* self) {} 118 | 119 | MyApplication* my_application_new() { 120 | return MY_APPLICATION(g_object_new(my_application_get_type(), 121 | "application-id", APPLICATION_ID, 122 | "flags", G_APPLICATION_NON_UNIQUE, 123 | nullptr)); 124 | } 125 | -------------------------------------------------------------------------------- /linux/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Project-level configuration. 2 | cmake_minimum_required(VERSION 3.10) 3 | project(runner 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 "jsontry") 8 | # The unique GTK application identifier for this application. See: 9 | # https://wiki.gnome.org/HowDoI/ChooseApplicationID 10 | set(APPLICATION_ID "com.rivafarabi.jsontry") 11 | 12 | # Explicitly opt in to modern CMake behaviors to avoid warnings with recent 13 | # versions of CMake. 14 | cmake_policy(SET CMP0063 NEW) 15 | 16 | # Load bundled libraries from the lib/ directory relative to the binary. 17 | set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") 18 | 19 | # Root filesystem for cross-building. 20 | if(FLUTTER_TARGET_PLATFORM_SYSROOT) 21 | set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) 22 | set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) 23 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 24 | set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) 25 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 26 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 27 | endif() 28 | 29 | # Define build configuration options. 30 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 31 | set(CMAKE_BUILD_TYPE "Debug" CACHE 32 | STRING "Flutter build mode" FORCE) 33 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 34 | "Debug" "Profile" "Release") 35 | endif() 36 | 37 | # Compilation settings that should be applied to most targets. 38 | # 39 | # Be cautious about adding new options here, as plugins use this function by 40 | # default. In most cases, you should add new options to specific targets instead 41 | # of modifying this function. 42 | function(APPLY_STANDARD_SETTINGS TARGET) 43 | target_compile_features(${TARGET} PUBLIC cxx_std_14) 44 | target_compile_options(${TARGET} PRIVATE -Wall -Werror) 45 | target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") 46 | target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") 47 | endfunction() 48 | 49 | # Flutter library and tool build rules. 50 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 51 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 52 | 53 | # System-level dependencies. 54 | find_package(PkgConfig REQUIRED) 55 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 56 | 57 | add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") 58 | 59 | # Define the application target. To change its name, change BINARY_NAME above, 60 | # not the value here, or `flutter run` will no longer work. 61 | # 62 | # Any new source files that you add to the application should be added here. 63 | add_executable(${BINARY_NAME} 64 | "main.cc" 65 | "my_application.cc" 66 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 67 | ) 68 | 69 | # Apply the standard set of build settings. This can be removed for applications 70 | # that need different build settings. 71 | apply_standard_settings(${BINARY_NAME}) 72 | 73 | # Add dependency libraries. Add any application-specific dependencies here. 74 | target_link_libraries(${BINARY_NAME} PRIVATE flutter) 75 | target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) 76 | 77 | # Run the Flutter tool portions of the build. This must not be removed. 78 | add_dependencies(${BINARY_NAME} flutter_assemble) 79 | 80 | # Only the install-generated bundle's copy of the executable will launch 81 | # correctly, since the resources must in the right relative locations. To avoid 82 | # people trying to run the unbundled copy, put it in a subdirectory instead of 83 | # the default top-level location. 84 | set_target_properties(${BINARY_NAME} 85 | PROPERTIES 86 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" 87 | ) 88 | 89 | 90 | # Generated plugin build rules, which manage building the plugins and adding 91 | # them to the application. 92 | include(flutter/generated_plugins.cmake) 93 | 94 | 95 | # === Installation === 96 | # By default, "installing" just makes a relocatable bundle in the build 97 | # directory. 98 | set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") 99 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 100 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 101 | endif() 102 | 103 | # Start with a clean build bundle directory every time. 104 | install(CODE " 105 | file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") 106 | " COMPONENT Runtime) 107 | 108 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 109 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") 110 | 111 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 112 | COMPONENT Runtime) 113 | 114 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 115 | COMPONENT Runtime) 116 | 117 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 118 | COMPONENT Runtime) 119 | 120 | foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) 121 | install(FILES "${bundled_library}" 122 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 123 | COMPONENT Runtime) 124 | endforeach(bundled_library) 125 | 126 | # Copy the native assets provided by the build.dart from all packages. 127 | set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") 128 | install(DIRECTORY "${NATIVE_ASSETS_DIR}" 129 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 130 | COMPONENT Runtime) 131 | 132 | # Fully re-copy the assets directory on each build to avoid having stale files 133 | # from a previous install. 134 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 135 | install(CODE " 136 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 137 | " COMPONENT Runtime) 138 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 139 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 140 | 141 | # Install the AOT library on non-Debug builds only. 142 | if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") 143 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 144 | COMPONENT Runtime) 145 | endif() 146 | -------------------------------------------------------------------------------- /lib/widgets/json_node_row.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:jsontry/models/json_node.dart'; 3 | import 'package:jsontry/providers/json_provider.dart'; 4 | import 'package:jsontry/utils/app_color_scheme.dart'; 5 | import 'package:jsontry/utils/style_cache.dart'; 6 | import 'package:native_context_menu/native_context_menu.dart'; 7 | 8 | class JsonNodeRow extends StatelessWidget { 9 | final JsonNode node; 10 | final JsonProvider provider; 11 | final int index; 12 | final AppColorScheme colorScheme; 13 | final StyleCache styleCache; 14 | final bool isSelected; 15 | final VoidCallback? onTap; 16 | final VoidCallback? onToggleTap; 17 | final Function(String action, JsonNode node)? onContextMenu; 18 | 19 | const JsonNodeRow({ 20 | super.key, 21 | required this.node, 22 | required this.provider, 23 | required this.index, 24 | required this.colorScheme, 25 | required this.styleCache, 26 | required this.isSelected, 27 | this.onTap, 28 | this.onToggleTap, 29 | this.onContextMenu, 30 | }); 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | final isEven = index % 2 == 0; 35 | final isSearchMatch = provider.isSearchMatch(node.path); 36 | final isCurrentResult = provider.isCurrentSearchResult(node.path); 37 | final isCollapsible = node.isCollapsible; 38 | final backgroundColor = _getBackgroundColor(isEven, isSelected, isSearchMatch, isCurrentResult); 39 | 40 | return ContextMenuRegion( 41 | onItemSelected: (item) => onContextMenu != null ? onContextMenu!(item.title, node) : null, 42 | menuItems: [ 43 | MenuItem(title: 'Copy Key'), 44 | MenuItem(title: 'Copy Value'), 45 | MenuItem( 46 | title: 'Copy Value As...', 47 | items: [ 48 | MenuItem(title: 'Formatted Value'), 49 | MenuItem(title: 'Minified Value'), 50 | ], 51 | ), 52 | MenuItem(title: 'Copy Path'), 53 | if (isCollapsible) MenuItem(title: node.isExpanded ? 'Collapse' : 'Expand'), 54 | ], 55 | child: GestureDetector( 56 | onTap: onTap, 57 | child: Container( 58 | height: 25, 59 | padding: EdgeInsets.only( 60 | left: (node.depth * 16.0), 61 | right: 12.0, 62 | top: 3.0, 63 | bottom: 3.0, 64 | ), 65 | decoration: BoxDecoration(color: backgroundColor), 66 | child: Row( 67 | children: [ 68 | SizedBox(width: (isCollapsible ? 8.0 : 30) + (node.depth * 8.0)), 69 | _buildExpansionIcon(isCollapsible), 70 | if (node.key != null) ...[ 71 | Text( 72 | '${node.key}', 73 | style: styleCache.keyStyle.copyWith(color: colorScheme.keyColor), 74 | ), 75 | Text( 76 | ' : ', 77 | style: styleCache.colonStyle.copyWith(color: colorScheme.keyColor), 78 | ), 79 | ], 80 | Expanded(child: _buildValueWidget()), 81 | const SizedBox(width: 8), 82 | _buildTypeIndicator(), 83 | ], 84 | ), 85 | ), 86 | ), 87 | ); 88 | } 89 | 90 | Color _getBackgroundColor(bool isEven, bool isSelected, bool isSearchMatch, bool isCurrentResult) { 91 | if (isSelected) return colorScheme.currentResultColor; 92 | if (isCurrentResult) return colorScheme.currentResultColor; 93 | if (isSearchMatch) return colorScheme.searchMatchColor; 94 | return isEven ? colorScheme.evenRowColor : colorScheme.oddRowColor; 95 | } 96 | 97 | Widget _buildExpansionIcon(bool isCollapsible) { 98 | if (!isCollapsible) return const SizedBox.shrink(); 99 | 100 | if (node.children == null || node.children!.isEmpty) { 101 | return const SizedBox.shrink(); 102 | } 103 | 104 | return GestureDetector( 105 | onTap: onToggleTap, 106 | child: Container( 107 | width: 14, 108 | height: 14, 109 | margin: const EdgeInsets.only(right: 8), 110 | decoration: BoxDecoration( 111 | border: Border.all(color: Colors.grey.shade400, width: 1), 112 | borderRadius: BorderRadius.circular(2), 113 | ), 114 | child: Icon( 115 | node.isExpanded ? Icons.remove : Icons.add, 116 | size: 10, 117 | color: colorScheme.expansionButtonColor, 118 | ), 119 | ), 120 | ); 121 | } 122 | 123 | Widget _buildValueWidget() { 124 | final style = styleCache.baseStyle.copyWith( 125 | color: colorScheme.getValueColor(node.type), 126 | ); 127 | 128 | switch (node.type) { 129 | case JsonNodeType.object: 130 | final objectMap = node.value as Map; 131 | return Text( 132 | node.isExpanded ? '{' : '{ ${objectMap.length} ${objectMap.length == 1 ? 'item' : 'items'} }', 133 | style: style.copyWith(fontWeight: FontWeight.w500), 134 | ); 135 | 136 | case JsonNodeType.array: 137 | final arrayList = node.value as List; 138 | return Text( 139 | node.isExpanded ? '[' : '[ ${arrayList.length} ${arrayList.length == 1 ? 'item' : 'items'} ]', 140 | style: style.copyWith(fontWeight: FontWeight.w500), 141 | ); 142 | 143 | case JsonNodeType.string: 144 | return Text( 145 | '"${node.value}"', 146 | style: style, 147 | overflow: TextOverflow.ellipsis, 148 | ); 149 | 150 | case JsonNodeType.number: 151 | return Text( 152 | node.value.toString(), 153 | style: style.copyWith(fontWeight: FontWeight.w500), 154 | ); 155 | 156 | case JsonNodeType.boolean: 157 | return Container( 158 | padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), 159 | child: Text( 160 | node.value.toString(), 161 | style: style.copyWith(fontWeight: FontWeight.w600), 162 | ), 163 | ); 164 | 165 | case JsonNodeType.nullValue: 166 | return Container( 167 | padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), 168 | child: Text( 169 | 'null', 170 | style: style.copyWith( 171 | fontStyle: FontStyle.italic, 172 | fontWeight: FontWeight.w500, 173 | ), 174 | ), 175 | ); 176 | } 177 | } 178 | 179 | Widget _buildTypeIndicator() { 180 | final typeColor = colorScheme.getTypeColor(node.type); 181 | final typeLabel = _getTypeLabel(); 182 | 183 | return Container( 184 | padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), 185 | decoration: BoxDecoration( 186 | color: typeColor.withOpacity(0.15), 187 | borderRadius: BorderRadius.circular(4), 188 | border: Border.all( 189 | color: typeColor.withOpacity(0.3), 190 | width: 1, 191 | ), 192 | ), 193 | child: Text( 194 | typeLabel, 195 | style: TextStyle( 196 | fontSize: 10, 197 | color: typeColor, 198 | fontWeight: FontWeight.w600, 199 | ), 200 | ), 201 | ); 202 | } 203 | 204 | String _getTypeLabel() { 205 | switch (node.type) { 206 | case JsonNodeType.object: 207 | final objectMap = node.value as Map; 208 | return 'Object (${objectMap.length})'; 209 | case JsonNodeType.array: 210 | final arrayList = node.value as List; 211 | return 'Array (${arrayList.length})'; 212 | case JsonNodeType.string: 213 | return 'String'; 214 | case JsonNodeType.number: 215 | return node.value is int ? 'Integer' : 'Number'; 216 | case JsonNodeType.boolean: 217 | return 'Boolean'; 218 | case JsonNodeType.nullValue: 219 | return 'Null'; 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /lib/widgets/status_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:jsontry/widgets/context_menu_button.dart'; 3 | import 'package:native_context_menu/native_context_menu.dart'; 4 | import 'package:provider/provider.dart'; 5 | import 'package:universal_platform/universal_platform.dart'; 6 | import 'package:macos_ui/macos_ui.dart'; 7 | import 'package:fluent_ui/fluent_ui.dart' as fluent; 8 | import '../providers/json_provider.dart'; 9 | 10 | class StatusBar extends StatelessWidget { 11 | const StatusBar({super.key}); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Consumer( 16 | builder: (context, provider, child) { 17 | return Column( 18 | children: [ 19 | if (provider.selectedNode != null) 20 | Container( 21 | height: 36, 22 | padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), 23 | decoration: BoxDecoration( 24 | color: _getBackgroundColor(context), 25 | border: Border( 26 | top: BorderSide( 27 | color: _getBorderColor(context), 28 | width: 1, 29 | ), 30 | ), 31 | ), 32 | child: Row( 33 | children: [ 34 | Container( 35 | padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), 36 | decoration: BoxDecoration( 37 | color: Colors.blue.withOpacity(0.1), 38 | borderRadius: BorderRadius.circular(4), 39 | ), 40 | child: Row( 41 | mainAxisSize: MainAxisSize.min, 42 | children: [ 43 | FittedBox( 44 | child: Icon( 45 | Icons.account_tree_outlined, 46 | size: 14, 47 | color: Colors.blue.withOpacity(0.8), 48 | ), 49 | ), 50 | const SizedBox(width: 4), 51 | Text( 52 | provider.selectedNode!.path, 53 | style: TextStyle(fontSize: 12, color: Colors.blue.withOpacity(0.8), fontWeight: FontWeight.w600, height: 1), 54 | ), 55 | ], 56 | ), 57 | ), 58 | const Spacer(), 59 | _buildNodePathMenu(provider) 60 | ], 61 | ), 62 | ), 63 | Container( 64 | height: 36, 65 | padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), 66 | decoration: BoxDecoration( 67 | color: _getBackgroundColor(context), 68 | border: Border( 69 | top: BorderSide( 70 | color: _getBorderColor(context), 71 | width: 1, 72 | ), 73 | ), 74 | ), 75 | child: Row( 76 | children: [ 77 | // File size 78 | if (provider.fileSize > 0) ...[ 79 | _buildStatusItem( 80 | context, 81 | Icons.storage, 82 | provider.fileSizeFormatted, 83 | Colors.green, 84 | ), 85 | const SizedBox(width: 12), 86 | ], 87 | 88 | // Load time 89 | if (provider.loadDuration != null) ...[ 90 | _buildStatusItem( 91 | context, 92 | Icons.timer, 93 | 'Loaded in ${_formatDuration(provider.loadDuration!)}', 94 | Colors.orange, 95 | ), 96 | const SizedBox(width: 12), 97 | ], 98 | 99 | const Spacer(), 100 | 101 | // Node count 102 | if (provider.nodes.isNotEmpty) ...[ 103 | _buildStatusItem( 104 | context, 105 | Icons.account_tree, 106 | '${provider.totalNodes} nodes', 107 | Colors.indigo, 108 | ), 109 | ], 110 | 111 | // Loading indicator 112 | if (provider.isLoading) ...[ 113 | const SizedBox(width: 12), 114 | SizedBox( 115 | width: 14, 116 | height: 14, 117 | child: CircularProgressIndicator( 118 | strokeWidth: 2, 119 | valueColor: AlwaysStoppedAnimation( 120 | Colors.blue.withOpacity(0.8), 121 | ), 122 | ), 123 | ), 124 | const SizedBox(width: 6), 125 | Text( 126 | 'Loading...', 127 | style: TextStyle( 128 | fontSize: 12, 129 | color: Colors.blue.withOpacity(0.8), 130 | fontWeight: FontWeight.w500, 131 | ), 132 | ), 133 | ], 134 | ], 135 | ), 136 | ), 137 | ], 138 | ); 139 | }, 140 | ); 141 | } 142 | 143 | Widget _buildStatusItem(BuildContext context, IconData icon, String text, Color color) { 144 | return Container( 145 | padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), 146 | decoration: BoxDecoration( 147 | color: color.withOpacity(0.1), 148 | borderRadius: BorderRadius.circular(4), 149 | ), 150 | child: Row( 151 | mainAxisSize: MainAxisSize.min, 152 | children: [ 153 | Icon( 154 | icon, 155 | size: 12, 156 | color: color.withOpacity(0.8), 157 | ), 158 | const SizedBox(width: 4), 159 | Text( 160 | text, 161 | style: TextStyle( 162 | fontSize: 11, 163 | color: color.withOpacity(0.8), 164 | fontWeight: FontWeight.w500, 165 | ), 166 | ), 167 | ], 168 | ), 169 | ); 170 | } 171 | 172 | Color _getBackgroundColor(BuildContext context) { 173 | if (UniversalPlatform.isMacOS) { 174 | return MacosTheme.of(context).canvasColor; 175 | } else if (UniversalPlatform.isWindows) { 176 | return fluent.FluentTheme.of(context).scaffoldBackgroundColor; 177 | } else { 178 | return Theme.of(context).scaffoldBackgroundColor; 179 | } 180 | } 181 | 182 | Color _getBorderColor(BuildContext context) { 183 | if (UniversalPlatform.isMacOS) { 184 | return MacosTheme.of(context).dividerColor; 185 | } else if (UniversalPlatform.isWindows) { 186 | return fluent.FluentTheme.of(context).resources.dividerStrokeColorDefault; 187 | } else { 188 | return Theme.of(context).dividerColor; 189 | } 190 | } 191 | 192 | String _formatDuration(Duration duration) { 193 | if (duration.inMilliseconds < 1000) { 194 | return '${duration.inMilliseconds}ms'; 195 | } else { 196 | return '${(duration.inMilliseconds / 1000).toStringAsFixed(2)}s'; 197 | } 198 | } 199 | 200 | _buildNodePathMenu(JsonProvider provider) { 201 | return ContextMenuButton( 202 | onItemSelected: (item) => provider.handleContextMenuAction(item.title, provider.selectedNode!), 203 | menuItems: [ 204 | MenuItem(title: 'Copy Key'), 205 | MenuItem(title: 'Copy Value'), 206 | MenuItem( 207 | title: 'Copy Value As...', 208 | items: [ 209 | MenuItem(title: 'Formatted Value'), 210 | MenuItem(title: 'Minified Value'), 211 | ], 212 | ), 213 | MenuItem(title: 'Copy Path'), 214 | ], 215 | ); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /windows/runner/win32_window.cpp: -------------------------------------------------------------------------------- 1 | #include "win32_window.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "resource.h" 7 | 8 | namespace { 9 | 10 | /// Window attribute that enables dark mode window decorations. 11 | /// 12 | /// Redefined in case the developer's machine has a Windows SDK older than 13 | /// version 10.0.22000.0. 14 | /// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute 15 | #ifndef DWMWA_USE_IMMERSIVE_DARK_MODE 16 | #define DWMWA_USE_IMMERSIVE_DARK_MODE 20 17 | #endif 18 | 19 | constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; 20 | 21 | /// Registry key for app theme preference. 22 | /// 23 | /// A value of 0 indicates apps should use dark mode. A non-zero or missing 24 | /// value indicates apps should use light mode. 25 | constexpr const wchar_t kGetPreferredBrightnessRegKey[] = 26 | L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; 27 | constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; 28 | 29 | // The number of Win32Window objects that currently exist. 30 | static int g_active_window_count = 0; 31 | 32 | using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); 33 | 34 | // Scale helper to convert logical scaler values to physical using passed in 35 | // scale factor 36 | int Scale(int source, double scale_factor) { 37 | return static_cast(source * scale_factor); 38 | } 39 | 40 | // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. 41 | // This API is only needed for PerMonitor V1 awareness mode. 42 | void EnableFullDpiSupportIfAvailable(HWND hwnd) { 43 | HMODULE user32_module = LoadLibraryA("User32.dll"); 44 | if (!user32_module) { 45 | return; 46 | } 47 | auto enable_non_client_dpi_scaling = 48 | reinterpret_cast( 49 | GetProcAddress(user32_module, "EnableNonClientDpiScaling")); 50 | if (enable_non_client_dpi_scaling != nullptr) { 51 | enable_non_client_dpi_scaling(hwnd); 52 | } 53 | FreeLibrary(user32_module); 54 | } 55 | 56 | } // namespace 57 | 58 | // Manages the Win32Window's window class registration. 59 | class WindowClassRegistrar { 60 | public: 61 | ~WindowClassRegistrar() = default; 62 | 63 | // Returns the singleton registrar instance. 64 | static WindowClassRegistrar* GetInstance() { 65 | if (!instance_) { 66 | instance_ = new WindowClassRegistrar(); 67 | } 68 | return instance_; 69 | } 70 | 71 | // Returns the name of the window class, registering the class if it hasn't 72 | // previously been registered. 73 | const wchar_t* GetWindowClass(); 74 | 75 | // Unregisters the window class. Should only be called if there are no 76 | // instances of the window. 77 | void UnregisterWindowClass(); 78 | 79 | private: 80 | WindowClassRegistrar() = default; 81 | 82 | static WindowClassRegistrar* instance_; 83 | 84 | bool class_registered_ = false; 85 | }; 86 | 87 | WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; 88 | 89 | const wchar_t* WindowClassRegistrar::GetWindowClass() { 90 | if (!class_registered_) { 91 | WNDCLASS window_class{}; 92 | window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); 93 | window_class.lpszClassName = kWindowClassName; 94 | window_class.style = CS_HREDRAW | CS_VREDRAW; 95 | window_class.cbClsExtra = 0; 96 | window_class.cbWndExtra = 0; 97 | window_class.hInstance = GetModuleHandle(nullptr); 98 | window_class.hIcon = 99 | LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); 100 | window_class.hbrBackground = 0; 101 | window_class.lpszMenuName = nullptr; 102 | window_class.lpfnWndProc = Win32Window::WndProc; 103 | RegisterClass(&window_class); 104 | class_registered_ = true; 105 | } 106 | return kWindowClassName; 107 | } 108 | 109 | void WindowClassRegistrar::UnregisterWindowClass() { 110 | UnregisterClass(kWindowClassName, nullptr); 111 | class_registered_ = false; 112 | } 113 | 114 | Win32Window::Win32Window() { 115 | ++g_active_window_count; 116 | } 117 | 118 | Win32Window::~Win32Window() { 119 | --g_active_window_count; 120 | Destroy(); 121 | } 122 | 123 | bool Win32Window::Create(const std::wstring& title, 124 | const Point& origin, 125 | const Size& size) { 126 | Destroy(); 127 | 128 | const wchar_t* window_class = 129 | WindowClassRegistrar::GetInstance()->GetWindowClass(); 130 | 131 | const POINT target_point = {static_cast(origin.x), 132 | static_cast(origin.y)}; 133 | HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); 134 | UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); 135 | double scale_factor = dpi / 96.0; 136 | 137 | HWND window = CreateWindow( 138 | window_class, title.c_str(), WS_OVERLAPPEDWINDOW, 139 | Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), 140 | Scale(size.width, scale_factor), Scale(size.height, scale_factor), 141 | nullptr, nullptr, GetModuleHandle(nullptr), this); 142 | 143 | if (!window) { 144 | return false; 145 | } 146 | 147 | UpdateTheme(window); 148 | 149 | return OnCreate(); 150 | } 151 | 152 | bool Win32Window::Show() { 153 | return ShowWindow(window_handle_, SW_SHOWNORMAL); 154 | } 155 | 156 | // static 157 | LRESULT CALLBACK Win32Window::WndProc(HWND const window, 158 | UINT const message, 159 | WPARAM const wparam, 160 | LPARAM const lparam) noexcept { 161 | if (message == WM_NCCREATE) { 162 | auto window_struct = reinterpret_cast(lparam); 163 | SetWindowLongPtr(window, GWLP_USERDATA, 164 | reinterpret_cast(window_struct->lpCreateParams)); 165 | 166 | auto that = static_cast(window_struct->lpCreateParams); 167 | EnableFullDpiSupportIfAvailable(window); 168 | that->window_handle_ = window; 169 | } else if (Win32Window* that = GetThisFromHandle(window)) { 170 | return that->MessageHandler(window, message, wparam, lparam); 171 | } 172 | 173 | return DefWindowProc(window, message, wparam, lparam); 174 | } 175 | 176 | LRESULT 177 | Win32Window::MessageHandler(HWND hwnd, 178 | UINT const message, 179 | WPARAM const wparam, 180 | LPARAM const lparam) noexcept { 181 | switch (message) { 182 | case WM_DESTROY: 183 | window_handle_ = nullptr; 184 | Destroy(); 185 | if (quit_on_close_) { 186 | PostQuitMessage(0); 187 | } 188 | return 0; 189 | 190 | case WM_DPICHANGED: { 191 | auto newRectSize = reinterpret_cast(lparam); 192 | LONG newWidth = newRectSize->right - newRectSize->left; 193 | LONG newHeight = newRectSize->bottom - newRectSize->top; 194 | 195 | SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, 196 | newHeight, SWP_NOZORDER | SWP_NOACTIVATE); 197 | 198 | return 0; 199 | } 200 | case WM_SIZE: { 201 | RECT rect = GetClientArea(); 202 | if (child_content_ != nullptr) { 203 | // Size and position the child window. 204 | MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, 205 | rect.bottom - rect.top, TRUE); 206 | } 207 | return 0; 208 | } 209 | 210 | case WM_ACTIVATE: 211 | if (child_content_ != nullptr) { 212 | SetFocus(child_content_); 213 | } 214 | return 0; 215 | 216 | case WM_DWMCOLORIZATIONCOLORCHANGED: 217 | UpdateTheme(hwnd); 218 | return 0; 219 | } 220 | 221 | return DefWindowProc(window_handle_, message, wparam, lparam); 222 | } 223 | 224 | void Win32Window::Destroy() { 225 | OnDestroy(); 226 | 227 | if (window_handle_) { 228 | DestroyWindow(window_handle_); 229 | window_handle_ = nullptr; 230 | } 231 | if (g_active_window_count == 0) { 232 | WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); 233 | } 234 | } 235 | 236 | Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { 237 | return reinterpret_cast( 238 | GetWindowLongPtr(window, GWLP_USERDATA)); 239 | } 240 | 241 | void Win32Window::SetChildContent(HWND content) { 242 | child_content_ = content; 243 | SetParent(content, window_handle_); 244 | RECT frame = GetClientArea(); 245 | 246 | MoveWindow(content, frame.left, frame.top, frame.right - frame.left, 247 | frame.bottom - frame.top, true); 248 | 249 | SetFocus(child_content_); 250 | } 251 | 252 | RECT Win32Window::GetClientArea() { 253 | RECT frame; 254 | GetClientRect(window_handle_, &frame); 255 | return frame; 256 | } 257 | 258 | HWND Win32Window::GetHandle() { 259 | return window_handle_; 260 | } 261 | 262 | void Win32Window::SetQuitOnClose(bool quit_on_close) { 263 | quit_on_close_ = quit_on_close; 264 | } 265 | 266 | bool Win32Window::OnCreate() { 267 | // No-op; provided for subclasses. 268 | return true; 269 | } 270 | 271 | void Win32Window::OnDestroy() { 272 | // No-op; provided for subclasses. 273 | } 274 | 275 | void Win32Window::UpdateTheme(HWND const window) { 276 | DWORD light_mode; 277 | DWORD light_mode_size = sizeof(light_mode); 278 | LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, 279 | kGetPreferredBrightnessRegValue, 280 | RRF_RT_REG_DWORD, nullptr, &light_mode, 281 | &light_mode_size); 282 | 283 | if (result == ERROR_SUCCESS) { 284 | BOOL enable_dark_mode = light_mode == 0; 285 | DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, 286 | &enable_dark_mode, sizeof(enable_dark_mode)); 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /lib/widgets/search_bar_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:provider/provider.dart'; 4 | import 'package:universal_platform/universal_platform.dart'; 5 | import 'package:macos_ui/macos_ui.dart'; 6 | import 'package:fluent_ui/fluent_ui.dart' as fluent; 7 | import '../providers/json_provider.dart'; 8 | 9 | class SearchBarWidget extends StatefulWidget { 10 | const SearchBarWidget({super.key}); 11 | 12 | @override 13 | State createState() => _SearchBarWidgetState(); 14 | } 15 | 16 | class _SearchBarWidgetState extends State { 17 | final TextEditingController _searchController = TextEditingController(); 18 | 19 | void _onSubmitted(String value) { 20 | if (value.length >= 3 || value.isEmpty) { 21 | if (UniversalPlatform.isMacOS) { 22 | Provider.of(context, listen: false).search(value); 23 | } else { 24 | context.read().search(value); 25 | } 26 | } 27 | } 28 | 29 | @override 30 | void dispose() { 31 | _searchController.dispose(); 32 | super.dispose(); 33 | } 34 | 35 | @override 36 | Widget build(BuildContext context) { 37 | if (UniversalPlatform.isMacOS) { 38 | return _buildMacOSSearchBar(context); 39 | } else if (UniversalPlatform.isWindows) { 40 | return _buildWindowsSearchBar(context); 41 | } else { 42 | return _buildMaterialSearchBar(context); 43 | } 44 | } 45 | 46 | Widget _buildMacOSSearchBar(BuildContext context) { 47 | return Consumer( 48 | builder: (context, provider, child) { 49 | bool isDark = MacosTheme.of(context).brightness == Brightness.dark; 50 | 51 | return Container( 52 | padding: const EdgeInsets.all(4), 53 | decoration: BoxDecoration( 54 | color: MacosTheme.of(context).canvasColor, 55 | border: Border( 56 | bottom: BorderSide( 57 | color: MacosTheme.of(context).dividerColor, 58 | width: 1, 59 | ), 60 | ), 61 | ), 62 | child: Row( 63 | children: [ 64 | Expanded( 65 | child: MacosSearchField( 66 | maxLines: 1, 67 | placeholder: 'Search keys and values (min 3 characters)...', 68 | onChanged: (value) { 69 | if (value.length >= 3 || value.isEmpty) { 70 | provider.search(value); 71 | } 72 | }, 73 | controller: _searchController, 74 | ), 75 | ), 76 | const SizedBox(width: 8), 77 | if (provider.searchQuery.isNotEmpty) ...[ 78 | Container( 79 | padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), 80 | decoration: BoxDecoration( 81 | color: Colors.grey.withOpacity(0.2), 82 | borderRadius: BorderRadius.circular(4), 83 | ), 84 | child: Text( 85 | provider.searchResultsCount > 0 ? '${provider.currentSearchIndex + 1}/${provider.searchResultsCount}' : '0/0', 86 | style: const TextStyle(fontSize: 12), 87 | ), 88 | ), 89 | const SizedBox(width: 4), 90 | MacosIconButton( 91 | icon: MacosIcon( 92 | CupertinoIcons.chevron_up, 93 | color: isDark ? CupertinoColors.systemGrey4 : CupertinoColors.black, 94 | ), 95 | onPressed: provider.searchResultsCount > 0 ? () => provider.previousSearchResult(context) : null, 96 | ), 97 | MacosIconButton( 98 | icon: MacosIcon( 99 | CupertinoIcons.chevron_down, 100 | color: isDark ? CupertinoColors.systemGrey4 : CupertinoColors.black, 101 | ), 102 | onPressed: provider.searchResultsCount > 0 ? () => provider.nextSearchResult(context) : null, 103 | ), 104 | const SizedBox(width: 4), 105 | MacosIconButton( 106 | icon: MacosIcon( 107 | CupertinoIcons.clear, 108 | color: isDark ? CupertinoColors.systemGrey4 : CupertinoColors.black, 109 | ), 110 | onPressed: () { 111 | _searchController.clear(); 112 | provider.search(''); 113 | }, 114 | ), 115 | ], 116 | ], 117 | ), 118 | ); 119 | }, 120 | ); 121 | } 122 | 123 | Widget _buildWindowsSearchBar(BuildContext context) { 124 | return Consumer( 125 | builder: (context, provider, child) { 126 | return Container( 127 | padding: const EdgeInsets.all(6), 128 | decoration: BoxDecoration( 129 | color: fluent.FluentTheme.of(context).menuColor, 130 | border: Border.symmetric( 131 | horizontal: BorderSide( 132 | color: fluent.FluentTheme.of(context).resources.dividerStrokeColorDefault, 133 | width: 1, 134 | ), 135 | ), 136 | ), 137 | child: Row( 138 | children: [ 139 | Expanded( 140 | child: fluent.TextBox( 141 | controller: _searchController, 142 | placeholder: 'Search keys and values (min 3 characters)...', 143 | prefix: const Padding( 144 | padding: EdgeInsets.only(left: 8), 145 | child: Icon(fluent.FluentIcons.search), 146 | ), 147 | suffix: context.read().isSearching ? const fluent.ProgressRing() : null, 148 | onSubmitted: _onSubmitted, 149 | ), 150 | ), 151 | if (provider.searchQuery.isNotEmpty) ...[ 152 | const SizedBox(width: 4), 153 | Container( 154 | padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), 155 | child: Text( 156 | provider.searchResultsCount > 0 ? '${provider.currentSearchIndex + 1}/${provider.searchResultsCount}' : '0/0', 157 | style: const TextStyle(fontSize: 12), 158 | ), 159 | ), 160 | const SizedBox(width: 4), 161 | fluent.IconButton( 162 | icon: const Icon(fluent.FluentIcons.chevron_up), 163 | onPressed: provider.searchResultsCount > 0 ? () => provider.previousSearchResult(context) : null, 164 | ), 165 | fluent.IconButton( 166 | icon: const Icon(fluent.FluentIcons.chevron_down), 167 | onPressed: provider.searchResultsCount > 0 ? () => provider.nextSearchResult(context) : null, 168 | ), 169 | const SizedBox(width: 4), 170 | const SizedBox(width: 8), 171 | fluent.IconButton( 172 | icon: const Icon(fluent.FluentIcons.clear), 173 | onPressed: () { 174 | _searchController.clear(); 175 | context.read().search(''); 176 | }, 177 | ), 178 | ], 179 | ], 180 | ), 181 | ); 182 | }, 183 | ); 184 | } 185 | 186 | Widget _buildMaterialSearchBar(BuildContext context) { 187 | return Container( 188 | padding: const EdgeInsets.all(16), 189 | decoration: BoxDecoration( 190 | color: Theme.of(context).scaffoldBackgroundColor, 191 | border: Border( 192 | bottom: BorderSide( 193 | color: Theme.of(context).dividerColor, 194 | width: 1, 195 | ), 196 | ), 197 | ), 198 | child: Row( 199 | children: [ 200 | Expanded( 201 | child: Container( 202 | decoration: BoxDecoration( 203 | color: Theme.of(context).brightness == Brightness.dark ? Colors.grey.shade800 : Colors.grey.shade100, 204 | borderRadius: BorderRadius.circular(8), 205 | border: Border.all( 206 | color: Theme.of(context).brightness == Brightness.dark ? Colors.grey.shade600 : Colors.grey.shade300, 207 | ), 208 | ), 209 | child: TextField( 210 | controller: _searchController, 211 | decoration: InputDecoration( 212 | hintText: 'Search keys and values (min 3 characters)...', 213 | hintStyle: TextStyle( 214 | color: Colors.grey.shade500, 215 | fontSize: 14, 216 | ), 217 | prefixIcon: Icon( 218 | Icons.search, 219 | color: Colors.grey.shade500, 220 | size: 20, 221 | ), 222 | border: InputBorder.none, 223 | contentPadding: const EdgeInsets.symmetric( 224 | horizontal: 16, 225 | vertical: 12, 226 | ), 227 | ), 228 | style: const TextStyle(fontSize: 14), 229 | onChanged: (value) { 230 | if (value.length >= 3 || value.isEmpty) { 231 | context.read().search(value); 232 | } 233 | }, 234 | onSubmitted: _onSubmitted, 235 | ), 236 | ), 237 | ), 238 | const SizedBox(width: 12), 239 | Container( 240 | decoration: BoxDecoration( 241 | color: Theme.of(context).brightness == Brightness.dark ? Colors.grey.shade800 : Colors.grey.shade100, 242 | borderRadius: BorderRadius.circular(6), 243 | border: Border.all( 244 | color: Theme.of(context).brightness == Brightness.dark ? Colors.grey.shade600 : Colors.grey.shade300, 245 | ), 246 | ), 247 | child: IconButton( 248 | icon: Icon( 249 | Icons.clear, 250 | size: 18, 251 | color: Colors.grey.shade600, 252 | ), 253 | onPressed: () { 254 | _searchController.clear(); 255 | context.read().search(''); 256 | }, 257 | ), 258 | ), 259 | ], 260 | ), 261 | ); 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /lib/widgets/windows_platform_menu.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart' hide MenuBar; 2 | import 'package:flutter/services.dart'; 3 | import 'package:fluent_ui/fluent_ui.dart' as fluent; 4 | import 'package:jsontry/models/json_node.dart'; 5 | import 'package:jsontry/providers/json_provider.dart'; 6 | import 'package:menu_bar/menu_bar.dart'; 7 | import 'package:package_info_plus/package_info_plus.dart'; 8 | import 'package:provider/provider.dart'; 9 | import 'package:url_launcher/url_launcher.dart'; 10 | 11 | class WindowsPlatformMenu extends StatefulWidget { 12 | final Widget child; 13 | 14 | const WindowsPlatformMenu({super.key, required this.child}); 15 | 16 | @override 17 | State createState() => _WindowsPlatformMenuState(); 18 | } 19 | 20 | class _WindowsPlatformMenuState extends State { 21 | Future _getAppVersion() async { 22 | final packageInfo = await PackageInfo.fromPlatform(); 23 | return packageInfo.version; 24 | } 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return Selector( 29 | selector: (_, provider) => provider.selectedNode != null, 30 | builder: (_, nodeSelected, ___) => Container( 31 | color: fluent.FluentTheme.of(context).menuColor, 32 | child: MenuBarWidget( 33 | barStyle: const MenuStyle( 34 | backgroundColor: WidgetStatePropertyAll(Colors.transparent), 35 | shadowColor: WidgetStatePropertyAll(Colors.transparent), 36 | surfaceTintColor: WidgetStatePropertyAll(Colors.transparent), 37 | ), 38 | barButtonStyle: ButtonStyle( 39 | alignment: Alignment.center, 40 | visualDensity: VisualDensity.compact, 41 | minimumSize: const WidgetStatePropertyAll(Size(0, 32)), 42 | textStyle: WidgetStatePropertyAll(fluent.FluentTheme.of(context).typography.caption!), 43 | ), 44 | menuButtonStyle: ButtonStyle( 45 | backgroundColor: WidgetStatePropertyAll(fluent.FluentTheme.of(context).micaBackgroundColor), 46 | minimumSize: const WidgetStatePropertyAll(Size(250, 42)), 47 | visualDensity: VisualDensity.compact, 48 | textStyle: WidgetStatePropertyAll(fluent.FluentTheme.of(context).typography.caption!), 49 | ), 50 | barButtons: [ 51 | BarButton( 52 | text: const Text('File'), 53 | submenu: SubMenu( 54 | menuItems: [ 55 | MenuButton( 56 | text: const Text('Open...'), 57 | shortcutText: 'Ctrl+O', 58 | onTap: context.read().loadJsonFile, 59 | ), 60 | MenuButton( 61 | text: const Text('Open from Clipboard...'), 62 | shortcutText: 'Ctrl+Shift+V', 63 | onTap: () => _openFromClipboard(context), 64 | ), 65 | MenuDivider( 66 | height: 0, 67 | color: fluent.FluentTheme.of(context).resources.dividerStrokeColorDefault, 68 | ), 69 | MenuButton( 70 | text: const Text('Exit'), 71 | shortcutText: 'Alt+F4', 72 | onTap: () => _exitApp(context), 73 | ), 74 | ], 75 | ), 76 | ), 77 | BarButton( 78 | text: const Text('Edit'), 79 | submenu: SubMenu( 80 | menuItems: [ 81 | MenuButton( 82 | text: const Text('Copy Selected Key'), 83 | shortcutText: 'Ctrl+K', 84 | onTap: nodeSelected ? () => _handleEditContextMenu("Copy Key") : null, 85 | ), 86 | MenuButton( 87 | text: const Text('Copy Selected Value'), 88 | shortcutText: 'Ctrl+C', 89 | onTap: nodeSelected ? () => _handleEditContextMenu("Copy Value") : null, 90 | ), 91 | MenuButton( 92 | text: const Text('Copy Selected Path'), 93 | shortcutText: 'Ctrl+P', 94 | onTap: nodeSelected ? () => _handleEditContextMenu("Copy Path") : null, 95 | ), 96 | ], 97 | ), 98 | ), 99 | BarButton( 100 | text: const Text('View'), 101 | submenu: SubMenu( 102 | menuItems: [ 103 | MenuButton( 104 | text: const Text('Expand All'), 105 | shortcutText: 'Ctrl+E', 106 | onTap: () => _handleViewAction("Expand All"), 107 | ), 108 | MenuButton( 109 | text: const Text('Collapse All'), 110 | shortcutText: 'Ctrl+R', 111 | onTap: () => _handleViewAction("Collapse All"), 112 | ), 113 | MenuDivider( 114 | height: 0, 115 | color: fluent.FluentTheme.of(context).resources.dividerStrokeColorDefault, 116 | ), 117 | MenuButton( 118 | text: const Text('Find...'), 119 | shortcutText: 'Ctrl+F', 120 | onTap: () => _handleViewAction("Find"), 121 | ), 122 | ], 123 | ), 124 | ), 125 | BarButton( 126 | text: const Text('Help'), 127 | submenu: SubMenu( 128 | menuItems: [ 129 | MenuButton( 130 | text: const Text('About JSONTry'), 131 | onTap: () => _showAboutDialog(context), 132 | ), 133 | ], 134 | ), 135 | ), 136 | ], 137 | child: widget.child, 138 | ), 139 | ), 140 | ); 141 | } 142 | 143 | void _exitApp(BuildContext context) { 144 | // Exit the application 145 | // You might want to show a confirmation dialog here 146 | // For now, we'll just close the app 147 | Navigator.of(context).pop(); 148 | } 149 | 150 | void _handleEditContextMenu(String action) { 151 | JsonNode? node = context.read().selectedNode; 152 | 153 | if (node == null) return; 154 | 155 | context.read().handleContextMenuAction(action, node); 156 | } 157 | 158 | void _handleViewAction(String action) { 159 | final provider = context.read(); 160 | 161 | switch (action) { 162 | case "Expand All": 163 | provider.expandAll(); 164 | break; 165 | case "Collapse All": 166 | provider.collapseAll(); 167 | break; 168 | case "Find": 169 | // Focus on search bar - this would need to be implemented 170 | // For now, we can just show a message 171 | _showInfoDialog(context, 'Use Ctrl+F to search within the JSON data'); 172 | break; 173 | } 174 | } 175 | 176 | Future _openFromClipboard(BuildContext context) async { 177 | try { 178 | final clipboardData = await Clipboard.getData('text/plain'); 179 | 180 | if (!context.mounted) return; 181 | 182 | if (clipboardData?.text != null && clipboardData!.text!.isNotEmpty) { 183 | await context.read().loadJsonFromString(clipboardData.text!); 184 | } else { 185 | _showErrorDialog(context, 'Clipboard is empty or does not contain text.'); 186 | } 187 | } catch (e) { 188 | if (context.mounted) { 189 | _showErrorDialog(context, 'Failed to read from clipboard: $e'); 190 | } 191 | } 192 | } 193 | 194 | void _showAboutDialog(BuildContext context) { 195 | showDialog( 196 | context: context, 197 | builder: (context) => fluent.ContentDialog( 198 | title: const Text('About JSONTry'), 199 | content: Column( 200 | mainAxisSize: MainAxisSize.min, 201 | crossAxisAlignment: CrossAxisAlignment.start, 202 | children: [ 203 | FutureBuilder( 204 | future: _getAppVersion(), 205 | builder: (context, snapshot) { 206 | if (snapshot.connectionState == ConnectionState.waiting) { 207 | return const Text('Loading version...'); 208 | } else if (snapshot.hasError) { 209 | return const Text('Version: Error loading version'); 210 | } else { 211 | return Text('JSONTry v${snapshot.data}'); 212 | } 213 | }), 214 | const SizedBox(height: 8), 215 | const Text('An open source JSON viewer.'), 216 | const SizedBox(height: 8), 217 | const Text('Features:'), 218 | const Text('• View and navigate large JSON files'), 219 | const Text('• Search through JSON data'), 220 | const Text('• Copy keys, values, and paths'), 221 | const Text('• Optimized performance for large files'), 222 | const SizedBox(height: 16), 223 | const Text('Made with ☕ by Riva Farabi.'), 224 | const Text('© 2025 Bigvaria. All rights reserved.'), 225 | ], 226 | ), 227 | actions: [ 228 | fluent.Button( 229 | child: const Text('GitHub'), 230 | onPressed: () { 231 | launchUrl(Uri.parse('https://github.com/rivafarabi/jsontry')); 232 | }), 233 | fluent.Button( 234 | child: const Text('OK'), 235 | onPressed: () => Navigator.of(context).pop(), 236 | ), 237 | ], 238 | ), 239 | ); 240 | } 241 | 242 | void _showErrorDialog(BuildContext context, String message) { 243 | showDialog( 244 | context: context, 245 | builder: (context) => fluent.ContentDialog( 246 | title: const Text('Error'), 247 | content: Text(message), 248 | actions: [ 249 | fluent.Button( 250 | child: const Text('OK'), 251 | onPressed: () => Navigator.of(context).pop(), 252 | ), 253 | ], 254 | ), 255 | ); 256 | } 257 | 258 | void _showInfoDialog(BuildContext context, String message) { 259 | showDialog( 260 | context: context, 261 | builder: (context) => fluent.ContentDialog( 262 | title: const Text('Information'), 263 | content: Text(message), 264 | actions: [ 265 | fluent.Button( 266 | child: const Text('OK'), 267 | onPressed: () => Navigator.of(context).pop(), 268 | ), 269 | ], 270 | ), 271 | ); 272 | } 273 | } 274 | --------------------------------------------------------------------------------