diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..dc7278f --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,28 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "example fl_query", + "cwd": "packages/example", + "request": "launch", + "type": "dart" + }, + { + "name": "example fl_query (profile mode)", + "cwd": "packages/example", + "request": "launch", + "type": "dart", + "flutterMode": "profile" + }, + { + "name": "example fl_query (release mode)", + "cwd": "packages/example", + "request": "launch", + "type": "dart", + "flutterMode": "release" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 0da109f..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "editor.tokenColorCustomizations": { - "comments": "", - "textMateRules": [] - } -} \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index 90995f5..0000000 --- a/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# fl-query - -Flutter Query, the asynchronous data fetching & invalidation library for Flutter \ No newline at end of file diff --git a/packages/example/.gitignore b/packages/example/.gitignore new file mode 100644 index 0000000..a8e938c --- /dev/null +++ b/packages/example/.gitignore @@ -0,0 +1,47 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/packages/example/.metadata b/packages/example/.metadata new file mode 100644 index 0000000..7fd5968 --- /dev/null +++ b/packages/example/.metadata @@ -0,0 +1,30 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled. + +version: + revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 + channel: stable + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 + base_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 + - platform: linux + create_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 + base_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/packages/example/README.md b/packages/example/README.md new file mode 100644 index 0000000..2b3fce4 --- /dev/null +++ b/packages/example/README.md @@ -0,0 +1,16 @@ +# example + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/packages/example/analysis_options.yaml b/packages/example/analysis_options.yaml new file mode 100644 index 0000000..61b6c4d --- /dev/null +++ b/packages/example/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/example/android/.gitignore b/packages/example/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/packages/example/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/packages/example/android/app/build.gradle b/packages/example/android/app/build.gradle new file mode 100644 index 0000000..2c9fb35 --- /dev/null +++ b/packages/example/android/app/build.gradle @@ -0,0 +1,71 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.github.KRTirtho.example" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/packages/example/android/app/src/debug/AndroidManifest.xml b/packages/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..d1c84d3 --- /dev/null +++ b/packages/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/packages/example/android/app/src/main/AndroidManifest.xml b/packages/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..7314066 --- /dev/null +++ b/packages/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/packages/example/android/app/src/main/kotlin/com/github/KRTirtho/example/MainActivity.kt b/packages/example/android/app/src/main/kotlin/com/github/KRTirtho/example/MainActivity.kt new file mode 100644 index 0000000..a3d8763 --- /dev/null +++ b/packages/example/android/app/src/main/kotlin/com/github/KRTirtho/example/MainActivity.kt @@ -0,0 +1,6 @@ +package com.github.KRTirtho.example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/packages/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/packages/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/example/android/app/src/main/res/drawable/launch_background.xml b/packages/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/packages/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/packages/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/packages/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/packages/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/packages/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/packages/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/packages/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/packages/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/packages/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/packages/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/packages/example/android/app/src/main/res/values-night/styles.xml b/packages/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/packages/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/example/android/app/src/main/res/values/styles.xml b/packages/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/packages/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/example/android/app/src/profile/AndroidManifest.xml b/packages/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..d1c84d3 --- /dev/null +++ b/packages/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/packages/example/android/build.gradle b/packages/example/android/build.gradle new file mode 100644 index 0000000..83ae220 --- /dev/null +++ b/packages/example/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.6.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.1.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/packages/example/android/gradle.properties b/packages/example/android/gradle.properties new file mode 100644 index 0000000..94adc3a --- /dev/null +++ b/packages/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/packages/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..cc5527d --- /dev/null +++ b/packages/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip diff --git a/packages/example/android/settings.gradle b/packages/example/android/settings.gradle new file mode 100644 index 0000000..44e62bc --- /dev/null +++ b/packages/example/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/packages/example/ios/.gitignore b/packages/example/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/packages/example/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/packages/example/ios/Flutter/AppFrameworkInfo.plist b/packages/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..8d4492f --- /dev/null +++ b/packages/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 9.0 + + diff --git a/packages/example/ios/Flutter/Debug.xcconfig b/packages/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/packages/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/packages/example/ios/Flutter/Release.xcconfig b/packages/example/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/packages/example/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/packages/example/ios/Runner.xcodeproj/project.pbxproj b/packages/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..1403370 --- /dev/null +++ b/packages/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,481 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.github.KRTirtho.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.github.KRTirtho.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.github.KRTirtho.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/packages/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/packages/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/packages/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/packages/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..c87d15a --- /dev/null +++ b/packages/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/packages/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/packages/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/packages/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/example/ios/Runner/AppDelegate.swift b/packages/example/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..70693e4 --- /dev/null +++ b/packages/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..28c6bf0 Binary files /dev/null and b/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..f091b6b Binary files /dev/null and b/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cde121 Binary files /dev/null and b/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..d0ef06e Binary files /dev/null and b/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..dcdc230 Binary files /dev/null and b/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..c8f9ed8 Binary files /dev/null and b/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..75b2d16 Binary files /dev/null and b/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..c4df70d Binary files /dev/null and b/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..6a84f41 Binary files /dev/null and b/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..d0e1f58 Binary files /dev/null and b/packages/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/packages/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/packages/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/packages/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/packages/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/packages/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/packages/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/packages/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/packages/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/packages/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/packages/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/packages/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/example/ios/Runner/Base.lproj/Main.storyboard b/packages/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/packages/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/example/ios/Runner/Info.plist b/packages/example/ios/Runner/Info.plist new file mode 100644 index 0000000..907f329 --- /dev/null +++ b/packages/example/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Example + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + CADisableMinimumFrameDurationOnPhone + + + diff --git a/packages/example/ios/Runner/Runner-Bridging-Header.h b/packages/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/packages/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/packages/example/lib/main.dart b/packages/example/lib/main.dart new file mode 100644 index 0000000..b9d091e --- /dev/null +++ b/packages/example/lib/main.dart @@ -0,0 +1,57 @@ +import 'package:fl_query/query_bowl.dart'; +import 'package:fl_query/query_builder.dart'; +import 'package:flutter/material.dart'; +import 'dart:async'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + primarySwatch: Colors.blue, + ), + home: const QueryBowlScope(child: MyHomePage()), + ); + } +} + +class MyHomePage extends StatelessWidget { + const MyHomePage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text("Fl Query Example"), + ), + body: Column( + children: [ + QueryBuilder( + queryKey: "greetings", + task: (queryKey) => Future.value("Welcome ($queryKey)"), + builder: (context, query) { + if (query.isLoading) return const CircularProgressIndicator(); + return Row( + children: [ + TextButton( + child: Text(query.data!), + onPressed: () async { + await query.refetch(); + }, + ), + ], + ); + }, + ) + ], + ), + ); + } +} diff --git a/packages/example/linux/.gitignore b/packages/example/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/packages/example/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/packages/example/linux/CMakeLists.txt b/packages/example/linux/CMakeLists.txt new file mode 100644 index 0000000..61e2a06 --- /dev/null +++ b/packages/example/linux/CMakeLists.txt @@ -0,0 +1,138 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "example") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.github.KRTirtho.example") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Define the application target. To change its name, change BINARY_NAME above, +# not the value here, or `flutter run` will no longer work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/packages/example/linux/flutter/CMakeLists.txt b/packages/example/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/packages/example/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/packages/example/linux/flutter/generated_plugin_registrant.cc b/packages/example/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..e71a16d --- /dev/null +++ b/packages/example/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void fl_register_plugins(FlPluginRegistry* registry) { +} diff --git a/packages/example/linux/flutter/generated_plugin_registrant.h b/packages/example/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..e0f0a47 --- /dev/null +++ b/packages/example/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/example/linux/flutter/generated_plugins.cmake b/packages/example/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..2e1de87 --- /dev/null +++ b/packages/example/linux/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/example/linux/main.cc b/packages/example/linux/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/packages/example/linux/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/packages/example/linux/my_application.cc b/packages/example/linux/my_application.cc new file mode 100644 index 0000000..0ba8f43 --- /dev/null +++ b/packages/example/linux/my_application.cc @@ -0,0 +1,104 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "example"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "example"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/packages/example/linux/my_application.h b/packages/example/linux/my_application.h new file mode 100644 index 0000000..72271d5 --- /dev/null +++ b/packages/example/linux/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/packages/example/macos/.gitignore b/packages/example/macos/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/packages/example/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/packages/example/macos/Flutter/Flutter-Debug.xcconfig b/packages/example/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..c2efd0b --- /dev/null +++ b/packages/example/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/example/macos/Flutter/Flutter-Release.xcconfig b/packages/example/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..c2efd0b --- /dev/null +++ b/packages/example/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/example/macos/Flutter/GeneratedPluginRegistrant.swift b/packages/example/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..cccf817 --- /dev/null +++ b/packages/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,10 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { +} diff --git a/packages/example/macos/Runner.xcodeproj/project.pbxproj b/packages/example/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..c84862c --- /dev/null +++ b/packages/example/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,572 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 51; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* example.app */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* example.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/packages/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/packages/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..fb7259e --- /dev/null +++ b/packages/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/packages/example/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/packages/example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/packages/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/example/macos/Runner/AppDelegate.swift b/packages/example/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..d53ef64 --- /dev/null +++ b/packages/example/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/packages/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/packages/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000..3c4935a Binary files /dev/null and b/packages/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/packages/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000..ed4cc16 Binary files /dev/null and b/packages/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/packages/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000..483be61 Binary files /dev/null and b/packages/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/packages/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000..bcbf36d Binary files /dev/null and b/packages/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/packages/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000..9c0a652 Binary files /dev/null and b/packages/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/packages/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000..e71a726 Binary files /dev/null and b/packages/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/packages/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000..8a31fe2 Binary files /dev/null and b/packages/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/packages/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/example/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..80e867a --- /dev/null +++ b/packages/example/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/example/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..d82f808 --- /dev/null +++ b/packages/example/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = example + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.github.KRTirtho.example + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2022 com.github.KRTirtho. All rights reserved. diff --git a/packages/example/macos/Runner/Configs/Debug.xcconfig b/packages/example/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/packages/example/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/packages/example/macos/Runner/Configs/Release.xcconfig b/packages/example/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/packages/example/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/packages/example/macos/Runner/Configs/Warnings.xcconfig b/packages/example/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/packages/example/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/packages/example/macos/Runner/DebugProfile.entitlements b/packages/example/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..dddb8a3 --- /dev/null +++ b/packages/example/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/packages/example/macos/Runner/Info.plist b/packages/example/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/packages/example/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/packages/example/macos/Runner/MainFlutterWindow.swift b/packages/example/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..2722837 --- /dev/null +++ b/packages/example/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController.init() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/packages/example/macos/Runner/Release.entitlements b/packages/example/macos/Runner/Release.entitlements new file mode 100644 index 0000000..852fa1a --- /dev/null +++ b/packages/example/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/packages/fl_query/example/pubspec.lock b/packages/example/pubspec.lock similarity index 53% rename from packages/fl_query/example/pubspec.lock rename to packages/example/pubspec.lock index 97410f2..bd3d6e1 100644 --- a/packages/fl_query/example/pubspec.lock +++ b/packages/example/pubspec.lock @@ -8,6 +8,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.8.2" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" charcode: dependency: transitive description: @@ -15,62 +29,79 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.1" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0" - crypto: - dependency: transitive + version: "1.16.0" + cupertino_icons: + dependency: "direct main" description: - name: crypto + name: cupertino_icons url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "1.0.4" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0" fl_query: dependency: "direct main" description: - path: ".." + path: "../fl_query" relative: true source: path version: "0.0.1" - hive: - dependency: transitive - description: - name: hive - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.5" - http: - dependency: transitive - description: - name: http - url: "https://pub.dartlang.org" - source: hosted - version: "0.13.4" - http_parser: - dependency: transitive - description: - name: http_parser - url: "https://pub.dartlang.org" - source: hosted - version: "4.0.0" - internet_connection_checker: - dependency: transitive - description: - name: internet_connection_checker - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.1+3" - lints: + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: dependency: "direct dev" + description: + name: flutter_lints + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + lints: + dependency: transitive description: name: lints url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "2.0.0" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.11" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.4" meta: dependency: transitive description: @@ -78,6 +109,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.7.0" + nested: + dependency: transitive + description: + name: nested + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" path: dependency: transitive description: @@ -85,6 +123,18 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.1" + provider: + dependency: transitive + description: + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.3" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" source_span: dependency: transitive description: @@ -92,6 +142,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.10.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" string_scanner: dependency: transitive description: @@ -106,19 +170,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.2.0" - typed_data: + test_api: dependency: transitive description: - name: typed_data + name: test_api url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" - uuid: + version: "0.4.9" + vector_math: dependency: transitive description: - name: uuid + name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "3.0.5" + version: "2.1.2" sdks: - dart: ">=2.15.1 <3.0.0" + dart: ">=2.17.1 <3.0.0" + flutter: ">=1.17.0" diff --git a/packages/example/pubspec.yaml b/packages/example/pubspec.yaml new file mode 100644 index 0000000..3464ab1 --- /dev/null +++ b/packages/example/pubspec.yaml @@ -0,0 +1,89 @@ +name: example +description: A new Flutter project. + +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: "none" # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +version: 1.0.0+1 + +environment: + sdk: ">=2.17.1 <3.0.0" + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + fl_query: + path: ../fl_query + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^2.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/packages/example/test/widget_test.dart b/packages/example/test/widget_test.dart new file mode 100644 index 0000000..092d222 --- /dev/null +++ b/packages/example/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:example/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/packages/example/web/favicon.png b/packages/example/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/packages/example/web/favicon.png differ diff --git a/packages/example/web/icons/Icon-192.png b/packages/example/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/packages/example/web/icons/Icon-192.png differ diff --git a/packages/example/web/icons/Icon-512.png b/packages/example/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/packages/example/web/icons/Icon-512.png differ diff --git a/packages/example/web/icons/Icon-maskable-192.png b/packages/example/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/packages/example/web/icons/Icon-maskable-192.png differ diff --git a/packages/example/web/icons/Icon-maskable-512.png b/packages/example/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/packages/example/web/icons/Icon-maskable-512.png differ diff --git a/packages/example/web/index.html b/packages/example/web/index.html new file mode 100644 index 0000000..41b3bc3 --- /dev/null +++ b/packages/example/web/index.html @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + example + + + + + + + + + + diff --git a/packages/example/web/manifest.json b/packages/example/web/manifest.json new file mode 100644 index 0000000..096edf8 --- /dev/null +++ b/packages/example/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "example", + "short_name": "example", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/packages/example/windows/.gitignore b/packages/example/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/packages/example/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/packages/example/windows/CMakeLists.txt b/packages/example/windows/CMakeLists.txt new file mode 100644 index 0000000..c027074 --- /dev/null +++ b/packages/example/windows/CMakeLists.txt @@ -0,0 +1,101 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(example LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "example") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/packages/example/windows/flutter/CMakeLists.txt b/packages/example/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..930d207 --- /dev/null +++ b/packages/example/windows/flutter/CMakeLists.txt @@ -0,0 +1,104 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + windows-x64 $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/packages/example/windows/flutter/generated_plugin_registrant.cc b/packages/example/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..8b6d468 --- /dev/null +++ b/packages/example/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void RegisterPlugins(flutter::PluginRegistry* registry) { +} diff --git a/packages/example/windows/flutter/generated_plugin_registrant.h b/packages/example/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/packages/example/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/example/windows/flutter/generated_plugins.cmake b/packages/example/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..b93c4c3 --- /dev/null +++ b/packages/example/windows/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/example/windows/runner/CMakeLists.txt b/packages/example/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..b9e550f --- /dev/null +++ b/packages/example/windows/runner/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/packages/example/windows/runner/Runner.rc b/packages/example/windows/runner/Runner.rc new file mode 100644 index 0000000..ef2303d --- /dev/null +++ b/packages/example/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#ifdef FLUTTER_BUILD_NUMBER +#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER +#else +#define VERSION_AS_NUMBER 1,0,0 +#endif + +#ifdef FLUTTER_BUILD_NAME +#define VERSION_AS_STRING #FLUTTER_BUILD_NAME +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.github.KRTirtho" "\0" + VALUE "FileDescription", "example" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "example" "\0" + VALUE "LegalCopyright", "Copyright (C) 2022 com.github.KRTirtho. All rights reserved." "\0" + VALUE "OriginalFilename", "example.exe" "\0" + VALUE "ProductName", "example" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/packages/example/windows/runner/flutter_window.cpp b/packages/example/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..b43b909 --- /dev/null +++ b/packages/example/windows/runner/flutter_window.cpp @@ -0,0 +1,61 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/packages/example/windows/runner/flutter_window.h b/packages/example/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/packages/example/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/packages/example/windows/runner/main.cpp b/packages/example/windows/runner/main.cpp new file mode 100644 index 0000000..bcb57b0 --- /dev/null +++ b/packages/example/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.CreateAndShow(L"example", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/packages/example/windows/runner/resource.h b/packages/example/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/packages/example/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/packages/example/windows/runner/resources/app_icon.ico b/packages/example/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000..c04e20c Binary files /dev/null and b/packages/example/windows/runner/resources/app_icon.ico differ diff --git a/packages/example/windows/runner/runner.exe.manifest b/packages/example/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..c977c4a --- /dev/null +++ b/packages/example/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/packages/example/windows/runner/utils.cpp b/packages/example/windows/runner/utils.cpp new file mode 100644 index 0000000..f5bf9fa --- /dev/null +++ b/packages/example/windows/runner/utils.cpp @@ -0,0 +1,64 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, utf8_string.data(), + target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/packages/example/windows/runner/utils.h b/packages/example/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/packages/example/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/packages/example/windows/runner/win32_window.cpp b/packages/example/windows/runner/win32_window.cpp new file mode 100644 index 0000000..c10f08d --- /dev/null +++ b/packages/example/windows/runner/win32_window.cpp @@ -0,0 +1,245 @@ +#include "win32_window.h" + +#include + +#include "resource.h" + +namespace { + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + FreeLibrary(user32_module); + } +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::CreateAndShow(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + return OnCreate(); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} diff --git a/packages/example/windows/runner/win32_window.h b/packages/example/windows/runner/win32_window.h new file mode 100644 index 0000000..17ba431 --- /dev/null +++ b/packages/example/windows/runner/win32_window.h @@ -0,0 +1,98 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates and shows a win32 window with |title| and position and size using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size to will treat the width height passed in to this function + // as logical pixels and scale to appropriate for the default monitor. Returns + // true if the window was created successfully. + bool CreateAndShow(const std::wstring& title, + const Point& origin, + const Size& size); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responsponds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/packages/fl_query/.gitignore b/packages/fl_query/.gitignore index 9be145f..96486fd 100644 --- a/packages/fl_query/.gitignore +++ b/packages/fl_query/.gitignore @@ -8,6 +8,7 @@ .buildlog/ .history .svn/ +migrate_working_dir/ # IntelliJ related *.iml diff --git a/packages/fl_query/.metadata b/packages/fl_query/.metadata index af84dae..756df28 100644 --- a/packages/fl_query/.metadata +++ b/packages/fl_query/.metadata @@ -4,7 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: 77d935af4db863f6abd0b9c31c7e6df2a13de57b + revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 channel: stable project_type: package diff --git a/packages/fl_query/example/.gitignore b/packages/fl_query/example/.gitignore deleted file mode 100644 index 3c8a157..0000000 --- a/packages/fl_query/example/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -# Files and directories created by pub. -.dart_tool/ -.packages - -# Conventional directory for build output. -build/ diff --git a/packages/fl_query/example/CHANGELOG.md b/packages/fl_query/example/CHANGELOG.md deleted file mode 100644 index effe43c..0000000 --- a/packages/fl_query/example/CHANGELOG.md +++ /dev/null @@ -1,3 +0,0 @@ -## 1.0.0 - -- Initial version. diff --git a/packages/fl_query/example/README.md b/packages/fl_query/example/README.md deleted file mode 100644 index a307539..0000000 --- a/packages/fl_query/example/README.md +++ /dev/null @@ -1 +0,0 @@ -A simple command-line application. diff --git a/packages/fl_query/example/analysis_options.yaml b/packages/fl_query/example/analysis_options.yaml deleted file mode 100644 index dee8927..0000000 --- a/packages/fl_query/example/analysis_options.yaml +++ /dev/null @@ -1,30 +0,0 @@ -# This file configures the static analysis results for your project (errors, -# warnings, and lints). -# -# This enables the 'recommended' set of lints from `package:lints`. -# This set helps identify many issues that may lead to problems when running -# or consuming Dart code, and enforces writing Dart using a single, idiomatic -# style and format. -# -# If you want a smaller set of lints you can change this to specify -# 'package:lints/core.yaml'. These are just the most critical lints -# (the recommended set includes the core lints). -# The core lints are also what is used by pub.dev for scoring packages. - -include: package:lints/recommended.yaml - -# Uncomment the following section to specify additional rules. - -# linter: -# rules: -# - camel_case_types - -# analyzer: -# exclude: -# - path/to/excluded/files/** - -# For more information about the core and recommended set of lints, see -# https://dart.dev/go/core-lints - -# For additional information about configuring this file, see -# https://dart.dev/guides/language/analysis-options diff --git a/packages/fl_query/example/bin/example.dart b/packages/fl_query/example/bin/example.dart deleted file mode 100644 index 8240894..0000000 --- a/packages/fl_query/example/bin/example.dart +++ /dev/null @@ -1,57 +0,0 @@ -import 'package:fl_query/fl_query.dart'; - -var todos = [ - {"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}, - { - "userId": 1, - "id": 2, - "title": "quis ut nam facilis et officia qui", - "completed": false - }, - {"userId": 1, "id": 3, "title": "fugiat veniam minus", "completed": false}, - {"userId": 1, "id": 4, "title": "et porro tempora", "completed": true}, - { - "userId": 1, - "id": 5, - "title": "laboriosam mollitia et enim quasi adipisci quia provident illum", - "completed": false - }, - { - "userId": 1, - "id": 6, - "title": "qui ullam ratione quibusdam voluptatem quia omnis", - "completed": false - }, -]; - -void main() async { - try { - var key = QueryKey("TEST"); - QueryClient queryClient = QueryClient(); - queryClient.mount(); - var data = await queryClient - .fetchQuery, dynamic, Map>( - queryKey: key, - queryFn: (context) { - return Future.value(todos.first); - }, - ); - print("======FETCHED DATA======"); - print(data); - print("======CACHED DATA======"); - print(queryClient.getQueryData(key)); - queryClient.setQueryData>(key, (prevData) { - return { - ...(prevData) ?? {}, - "title": "Yehi aloh heh", - "completed": true, - }; - }); - print("======CACHED DATA======"); - print(queryClient.getQueryData(key)); - print("======STATE======"); - print(queryClient.getQueryState(key)?.toJson()); - } catch (e) { - print(e); - } -} diff --git a/packages/fl_query/example/pubspec.yaml b/packages/fl_query/example/pubspec.yaml deleted file mode 100644 index 2fcdf31..0000000 --- a/packages/fl_query/example/pubspec.yaml +++ /dev/null @@ -1,16 +0,0 @@ -name: example -description: A simple command-line application. -version: 1.0.0 -# homepage: https://www.example.com -publish_to: none - -environment: - sdk: '>=2.15.1 <3.0.0' - - -dependencies: - fl_query: - path: ../ - -dev_dependencies: - lints: ^1.0.0 diff --git a/packages/fl_query/lib/fl_query.dart b/packages/fl_query/lib/fl_query.dart index a44b61b..fd3a379 100644 --- a/packages/fl_query/lib/fl_query.dart +++ b/packages/fl_query/lib/fl_query.dart @@ -1,4 +1,19 @@ library fl_query; -export 'package:fl_query/src/core/core.dart'; -// export 'package:fl_query/src/core/framework.dart'; +import 'package:flutter/material.dart'; + +class FlQueryScope extends StatefulWidget { + final Widget child; + const FlQueryScope({required this.child, Key? key}) : super(key: key); + + @override + State createState() => _FlQueryScopeState(); +} + +class _FlQueryScopeState extends State { + @override + Widget build(BuildContext context) { + Theme.of(context); + return widget.child; + } +} diff --git a/packages/fl_query/lib/query.dart b/packages/fl_query/lib/query.dart new file mode 100644 index 0000000..4db715e --- /dev/null +++ b/packages/fl_query/lib/query.dart @@ -0,0 +1,108 @@ +import 'dart:async'; + +import 'package:flutter/widgets.dart'; + +enum QueryStatus { + failed, + succeed, + pending, + refetching; +} + +typedef QueryTaskFunction = FutureOr Function(String); + +typedef QueryListener = FutureOr Function(T); + +typedef ListenerUnsubscriber = void Function(); + +class Query extends ChangeNotifier { + // all params + final String queryKey; + QueryTaskFunction task; + final int retries; + final Duration retryDelay; + + // all properties + T? data; + dynamic error; + QueryStatus status; + int retryAttempts = 0; + late DateTime updatedAt; + int refetchCount = 0; + + @protected + bool fetched = false; + + final QueryListener? _onData; + final QueryListener? _onError; + + Query({ + required this.queryKey, + required this.task, + this.retries = 3, + this.retryDelay = const Duration(milliseconds: 200), + T? initialData, + QueryListener? onData, + QueryListener? onError, + }) : status = QueryStatus.pending, + data = initialData, + _onData = onData, + _onError = onError; + + // all getters & setters + bool get hasData => data != null && error == null; + bool get hasError => + status == QueryStatus.failed && error != null && data == null; + bool get isLoading => + status == QueryStatus.pending && data == null && error == null; + bool get isRefetching => + status == QueryStatus.refetching && data == null && error == null; + bool get isSucceeded => status == QueryStatus.succeed && data != null; + + // all methods + Future _execute({bool isFetch = true}) async { + try { + retryAttempts = 0; + status = isFetch ? QueryStatus.pending : QueryStatus.refetching; + data = await task(queryKey); + updatedAt = DateTime.now(); + status = QueryStatus.succeed; + _onData?.call(data!); + notifyListeners(); + } catch (e) { + status = QueryStatus.failed; + error = e; + _onError?.call(e); + notifyListeners(); + // retrying for retry count if failed for the first time + while (retryAttempts <= retries) { + await Future.delayed(retryDelay); + try { + data = await task(queryKey); + status = QueryStatus.succeed; + _onData?.call(data!); + notifyListeners(); + break; + } catch (e) { + status = QueryStatus.failed; + error = e; + retryAttempts++; + _onError?.call(e); + notifyListeners(); + } + } + } + } + + Future fetch() async { + return _execute().then((_) { + fetched = true; + return data; + }); + } + + Future refetch() { + refetchCount++; + return _execute(isFetch: false).then((_) => data); + } +} diff --git a/packages/fl_query/lib/query_bowl.dart b/packages/fl_query/lib/query_bowl.dart new file mode 100644 index 0000000..2e4fb20 --- /dev/null +++ b/packages/fl_query/lib/query_bowl.dart @@ -0,0 +1,98 @@ +import 'package:fl_query/query.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter/widgets.dart'; + +class QueryBowlScope extends StatefulWidget { + final Widget child; + final Duration? staleTime; + const QueryBowlScope({ + required this.child, + this.staleTime, + Key? key, + }) : super(key: key); + + @override + State createState() => _QueryBowlScopeState(); +} + +class _QueryBowlScopeState extends State { + late Set queries; + + @override + void initState() { + super.initState(); + queries = {}; + } + + void updateQueries() { + setState(() { + queries = Set.from(queries); + }); + } + + @override + Widget build(BuildContext context) { + return QueryBowl( + onUpdate: updateQueries, + queries: queries, + child: widget.child, + ); + } +} + +class QueryBowl extends InheritedWidget { + final Set queries; + final Duration staleTime; + final void Function() onUpdate; + + const QueryBowl({ + required Widget child, + required this.onUpdate, + required this.queries, + this.staleTime = const Duration(minutes: 5), + Key? key, + }) : super(child: child, key: key); + + listenToQueryUpdate() { + for (final query in queries) { + query.addListener(onUpdate); + } + } + + void disposeListeners() { + for (final query in queries) { + query.removeListener(onUpdate); + } + } + + Future fetchQuery(Query query) async { + final prevQuery = + queries.firstWhereOrNull((q) => q.queryKey == query.queryKey); + if (prevQuery is Query) { + if (!prevQuery.hasData) { + return prevQuery.fetched + ? await prevQuery.refetch() + : await prevQuery.fetch(); + } + return prevQuery.data; + } + queries.add(query); + disposeListeners(); + listenToQueryUpdate(); + return await query.fetch(); + } + + Query? getQuery(String queryKey) { + return queries.firstWhereOrNull( + (query) => query.queryKey == queryKey && query is Query) + as Query?; + } + + static QueryBowl of(BuildContext context) => + context.dependOnInheritedWidgetOfExactType()!; + + @override + bool updateShouldNotify(QueryBowl oldWidget) { + return oldWidget.staleTime != staleTime || oldWidget.queries != queries; + } +} diff --git a/packages/fl_query/lib/query_builder.dart b/packages/fl_query/lib/query_builder.dart new file mode 100644 index 0000000..07565da --- /dev/null +++ b/packages/fl_query/lib/query_builder.dart @@ -0,0 +1,36 @@ +import 'package:fl_query/query.dart'; +import 'package:fl_query/query_bowl.dart'; +import 'package:flutter/widgets.dart'; + +class QueryBuilder extends StatefulWidget { + final Widget Function(BuildContext, Query) builder; + final QueryTaskFunction task; + final String queryKey; + const QueryBuilder({ + required this.builder, + required this.task, + required this.queryKey, + Key? key, + }) : super(key: key); + + @override + State> createState() => _QueryBuilderState(); +} + +class _QueryBuilderState extends State> { + late Query query; + @override + void initState() { + super.initState(); + query = Query(queryKey: widget.queryKey, task: widget.task); + WidgetsBinding.instance.addPostFrameCallback((_) async { + await QueryBowl.of(context).fetchQuery(query); + }); + } + + @override + Widget build(BuildContext context) { + final queryRT = QueryBowl.of(context).getQuery(widget.queryKey) ?? query; + return widget.builder(context, queryRT); + } +} diff --git a/packages/fl_query/lib/src/core/core.dart b/packages/fl_query/lib/src/core/core.dart deleted file mode 100644 index c00ff42..0000000 --- a/packages/fl_query/lib/src/core/core.dart +++ /dev/null @@ -1,21 +0,0 @@ -export 'package:fl_query/src/core/retryer.dart' show CancelledError; -export 'package:fl_query/src/core/query_cache.dart' show QueryCache; -export 'package:fl_query/src/core/query_client.dart' show QueryClient; -export 'package:fl_query/src/core/query_observer.dart' show QueryObserver; -export 'package:fl_query/src/core/query_key.dart'; -// export 'package:fl_query/src/core/queriesObserver.dart' show QueriesObserver; -// export 'package:fl_query/src/core/infiniteQueryObserver.dart' show InfiniteQueryObserver; -// export 'package:fl_query/src/core/mutationCache.dart' show MutationCache; -// export 'package:fl_query/src/core/mutationObserver.dart' show MutationObserver; -// export 'package:fl_query/src/core/logger.dart' show setLogger; -export 'package:fl_query/src/core/notify_manager.dart' show notifyManager; -// export 'package:fl_query/src/core/focusManager.dart' show focusManager; -export 'package:fl_query/src/core/online_manager.dart' show onlineManager; -export 'package:fl_query/src/core/utils.dart' show hashQueryKey; -export 'package:fl_query/src/core/retryer.dart' show isCancelledError; -// export 'package:fl_query/src/core/hydration.dart' show dehydrate, DehydrateOptions, DehydratedState, HydrateOptions, ShouldDehydrateMutationFunction, ShouldDehydrateQueryFunction; - -export 'package:fl_query/src/core/models.dart'; -export 'package:fl_query/src/core/query.dart' show Query; -// export type { Mutation } from './mutation' -// export type { Logger } from './logger' \ No newline at end of file diff --git a/packages/fl_query/lib/src/core/models.dart b/packages/fl_query/lib/src/core/models.dart deleted file mode 100644 index 2abc42c..0000000 --- a/packages/fl_query/lib/src/core/models.dart +++ /dev/null @@ -1,596 +0,0 @@ -import 'dart:async'; - -import 'package:fl_query/src/core/query.dart'; -import 'package:fl_query/src/core/query_key.dart'; -import 'package:fl_query/src/core/retryer.dart'; - -typedef QueryMeta = Map; -typedef QueryKeyHashFunction = String Function(QueryKey queryKey); -typedef QueryFunction = FutureOr Function( - QueryFunctionContext context, -); -typedef GetPreviousPageParamFunction> - = Function( - TQueryFnData firstPage, - List allPages, -); -typedef GetNextPageParamFunction> - = Function( - TQueryFnData firstPage, - List allPages, -); - -class QueryOptions, TError, - TData extends Map> { - ShouldRetryFunction? retry; - RetryDelayFunction? retryDelay; - Duration? cacheTime; - bool Function(TData? oldData, TData newData)? isDataEqual; - QueryFunction? queryFn; - QueryKey? queryKey; - - /// Basically [QueryKey.key] in short form - String? queryHash; - QueryKeyHashFunction? queryKeyHashFn; - TData? initialData; - DateTime? initialDataUpdatedAt; - QueryBehavior? behavior; - - /// Set this to `false` to disable structural sharing between query results\ - /// Defaults to `true`. - bool? structuralSharing; - - /// This function can be set to automatically get the previous cursor for infinite queries. - /// The result will also be used to determine the value of `hasPreviousPage`. - GetPreviousPageParamFunction? getPreviousPageParam; - - /// This function can be set to automatically get the next cursor for - /// infinite queries. - /// The result will also be used to determine the value of - /// `hasNextPage`. - GetNextPageParamFunction? getNextPageParam; - bool? defaulted; - - /// Additional payload to be stored on each query. - /// Use this property to pass information that can be used in other places. - QueryMeta? meta; - - QueryOptions({ - this.retry, - this.retryDelay, - this.queryKey, - this.queryKeyHashFn, - this.cacheTime, - this.isDataEqual, - this.queryFn, - this.defaulted, - this.initialData, - this.initialDataUpdatedAt, - this.meta, - this.queryHash, - this.structuralSharing, - this.getPreviousPageParam, - this.getNextPageParam, - this.behavior, - }); - - QueryOptions.fromJson(Map json) { - queryKey = json["queryKey"]; - queryKeyHashFn = json["queryKeyHashFn"]; - cacheTime = json["cacheTime"]; - isDataEqual = json["isDataEqual"]; - queryFn = json["queryFn"]; - queryHash = json["queryHash"]; - initialData = json["initialData"]; - initialDataUpdatedAt = json["initialDataUpdatedAt"]; - meta = json["meta"]; - structuralSharing = json["structuralSharing"]; - defaulted = json["defaulted"]; - retry = json["retry"]; - retryDelay = json["retryDelay"]; - behavior = json["behavior"]; - getPreviousPageParam = json["getPreviousPageParam"]; - getNextPageParam = json["getNextPageParam"]; - } - - Map toJson() { - return { - "queryKey": queryKey, - "queryKeyHashFn": queryKeyHashFn, - "cacheTime": cacheTime, - "isDataEqual": isDataEqual, - "queryFn": queryFn, - "queryHash": queryHash, - "initialData": initialData, - "initialDataUpdatedAt": initialDataUpdatedAt, - "meta": meta, - "structuralSharing": structuralSharing, - "defaulted": defaulted, - "retry": retry, - "retryDelay": retryDelay, - "behavior": behavior, - "getPreviousPageParam": getPreviousPageParam, - "getNextPageParam": getNextPageParam, - }; - } -} - -class QueryFilters { - bool? active; - bool? exact; - bool? inactive; - bool Function(Query query)? predicate; - QueryKey? queryKey; - bool? stale; - bool? fetching; - - QueryFilters({ - this.active, - this.exact, - this.inactive, - this.predicate, - this.queryKey, - this.stale, - this.fetching, - }); - - Map toJson() { - return { - "active": active, - "exact": exact, - "inactive": inactive, - "queryKey": queryKey, - "stale": stale, - "fetching": fetching, - "predicate": predicate, - }; - } -} - -class RefetchPageFilters { - bool Function(TPageData lastPage, int index, List allPages)? - refetchPage; -} - -class RefetchableQueryFilters extends QueryFilters - implements RefetchPageFilters { - @override - bool Function(TPageData lastPage, int index, List allPages)? - refetchPage; - RefetchableQueryFilters({ - bool? active, - bool? exact, - bool? inactive, - bool Function(Query query)? predicate, - QueryKey? queryKey, - bool? stale, - bool? fetching, - this.refetchPage, - }) : super( - active: active, - exact: exact, - fetching: fetching, - inactive: inactive, - predicate: predicate, - queryKey: queryKey, - stale: stale, - ); - - RefetchableQueryFilters.fromJson(Map json) { - active = json["active"]; - exact = json["exact"]; - inactive = json["inactive"]; - queryKey = json["queryKey"]; - stale = json["stale"]; - fetching = json["fetching"]; - predicate = json["predicate"]; - refetchPage = json["refetchPage"]; - } - - @override - Map toJson() { - return { - "active": active, - "exact": exact, - "inactive": inactive, - "queryKey": queryKey, - "stale": stale, - "fetching": fetching, - "predicate": predicate, - "refetchPage": refetchPage, - }; - } -} - -class InvalidateQueryFilters - extends RefetchableQueryFilters { - bool? refetchActive; - bool? refetchInactive; - - InvalidateQueryFilters({ - bool? active, - bool? exact, - bool? inactive, - bool Function(Query query)? predicate, - QueryKey? queryKey, - bool? stale, - bool? fetching, - bool Function(TPageData lastPage, int index, List allPages)? - refetchPage, - this.refetchActive, - this.refetchInactive, - }) : super( - active: active, - exact: exact, - fetching: fetching, - inactive: inactive, - predicate: predicate, - queryKey: queryKey, - stale: stale, - refetchPage: refetchPage, - ); - - InvalidateQueryFilters.fromJson(Map json) - : super.fromJson(json) { - refetchActive = json["refetchActive"]; - refetchInactive = json["refetchInactive"]; - } - - @override - Map toJson() { - return { - ...super.toJson(), - "refetchActive": refetchActive, - "refetchInactive": refetchInactive, - }; - } -} - -class RefetchOptions { - bool? throwOnError; - bool? cancelRefetch; - RefetchOptions({ - this.cancelRefetch, - this.throwOnError, - }); -} - -enum QueryStatus { - idle, - loading, - error, - success, -} - -class QueryObserverResult, TError> { - TData? data; - DateTime? dataUpdatedAt; - TError? error; - DateTime? errorUpdatedAt; - int failureCount; - bool isError; - bool isFetched; - bool isFetchedAfterMount; - bool isFetching; - bool isIdle; - bool isLoading; - bool isLoadingError; - bool isPlaceholderData; - bool isPreviousData; - bool isRefetchError; - bool isRefetching; - bool isStale; - bool isSuccess; - Future?> Function({ - RefetchOptions options, - RefetchableQueryFilters filters, - }) refetch; - void Function() remove; - QueryStatus status; - - QueryObserverResult({ - required this.failureCount, - required this.isError, - required this.isFetched, - required this.isFetchedAfterMount, - required this.isFetching, - required this.isIdle, - required this.isLoading, - required this.isLoadingError, - required this.isPlaceholderData, - required this.isPreviousData, - required this.isRefetchError, - required this.isRefetching, - required this.isStale, - required this.isSuccess, - required this.refetch, - required this.remove, - required this.status, - this.data, - this.error, - this.dataUpdatedAt, - this.errorUpdatedAt, - }) { - String errorLabel = - "[QueryObserverResult.QueryObserverResult] status = `$status` but parent has wrong set of properties"; - if (status == QueryStatus.idle && - (data != null || - error != null || - isError || - !isIdle || - isLoading || - isLoadingError || - isRefetchError || - isSuccess)) throw Exception(errorLabel); - - if (status == QueryStatus.loading && - (data != null || - error != null || - isError || - isIdle || - !isLoading || - isLoadingError || - isRefetchError || - isSuccess != false)) throw Exception(errorLabel); - - if (status == QueryStatus.error && - ((!(error is TError)) || !isError || isIdle || isLoading || isSuccess)) - throw Exception(errorLabel); - - if (status == QueryStatus.success && - (!(data is TData) || - error != null || - isError || - isIdle || - isLoading || - isLoadingError || - isRefetchError || - !isSuccess)) throw Exception(errorLabel); - } - - Map toJson() { - final Map data = { - 'data': this.data, - 'dataUpdatedAt': dataUpdatedAt, - 'error': error, - 'errorUpdatedAt': errorUpdatedAt, - 'failureCount': failureCount, - 'isError': isError, - 'isFetched': isFetched, - 'isFetchedAfterMount': isFetchedAfterMount, - 'isFetching': isFetching, - 'isIdle': isIdle, - 'isLoading': isLoading, - 'isLoadingError': isLoadingError, - 'isPlaceholderData': isPlaceholderData, - 'isPreviousData': isPreviousData, - 'isRefetchError': isRefetchError, - 'isRefetching': isRefetching, - 'isStale': isStale, - 'isSuccess': isSuccess, - 'refetch': refetch, - 'remove': remove, - 'status': status, - }; - return data; - } -} - -typedef RefetchIntervalFunction< - TQueryFnData extends Map, - TError, - TQueryData extends Map, - TData extends Map> - = Duration? Function( - TData? data, - Query query, -); - -enum RefetchOnReconnect { - on, - off, - always, -} - -enum RefetchOnMount { - on, - off, - always, -} - -class QueryObserverOptions< - TQueryFnData extends Map, - TError, - TData extends Map, - TQueryData extends Map> - extends QueryOptions { - bool? enabled; - Duration? staleTime; - RefetchIntervalFunction? - refetchInterval; - bool? refetchIntervalInBackground; - RefetchOnReconnect? refetchOnReconnect; - RefetchOnMount? refetchOnMount; - bool? retryOnMount; - OnData? onSuccess; - OnError? onError; - void Function(TData? data, [TError? error])? onSettled; - bool Function(TError error)? useErrorBoundary; - TData Function(TQueryData? data)? select; - bool? suspense; - bool? keepPreviousData; - TQueryData? placeholderData; - bool? optimisticResults; - /*List|'tracked'?*/ dynamic notifyOnChangeProps; - List? notifyOnChangePropsExclusions; - - QueryObserverOptions({ - this.enabled, - this.staleTime, - this.refetchInterval, - this.refetchIntervalInBackground, - this.refetchOnReconnect, - this.refetchOnMount, - this.retryOnMount, - this.onSuccess, - this.onError, - this.onSettled, - this.useErrorBoundary, - this.select, - this.suspense, - this.keepPreviousData, - this.placeholderData, - this.optimisticResults, - QueryKey? queryKey, - QueryKeyHashFunction? queryKeyHashFn, - Duration? cacheTime, - bool Function(TQueryData? oldData, TQueryData newData)? isDataEqual, - QueryFunction? queryFn, - String? queryHash, - TQueryData? initialData, - DateTime? initialDataUpdatedAt, - QueryMeta? meta, - bool? structuralSharing, - bool? defaulted, - ShouldRetryFunction? retry, - RetryDelayFunction? retryDelay, - QueryBehavior? behavior, - GetPreviousPageParamFunction? getPreviousPageParam, - GetNextPageParamFunction? getNextPageParam, - }) : super( - queryKey: queryKey, - queryKeyHashFn: queryKeyHashFn, - cacheTime: cacheTime, - isDataEqual: isDataEqual, - queryFn: queryFn, - queryHash: queryHash, - initialData: initialData, - initialDataUpdatedAt: initialDataUpdatedAt, - meta: meta, - structuralSharing: structuralSharing, - defaulted: defaulted, - behavior: behavior, - getNextPageParam: getNextPageParam, - getPreviousPageParam: getPreviousPageParam, - retry: retry, - retryDelay: retryDelay, - ); - - QueryObserverOptions.fromJson(Map json) - : enabled = json["enabled"], - staleTime = json["staleTime"], - refetchInterval = json["refetchInterval"], - refetchIntervalInBackground = json["refetchIntervalInBackground"], - refetchOnReconnect = json["refetchOnReconnect"], - refetchOnMount = json["refetchOnMount"], - retryOnMount = json["retryOnMount"], - onSuccess = json["onSuccess"], - onError = json["onError"], - onSettled = json["onSettled"], - useErrorBoundary = json["useErrorBoundary"], - select = json["select"], - suspense = json["suspense"], - keepPreviousData = json["keepPreviousData"], - placeholderData = json["placeholderData"], - optimisticResults = json["optimisticResults"], - super.fromJson(json); - - @override - Map toJson() { - return { - ...super.toJson(), - "enabled": enabled, - "staleTime": staleTime, - "refetchInterval": refetchInterval, - "refetchIntervalInBackground": refetchIntervalInBackground, - "refetchOnReconnect": refetchOnReconnect, - "refetchOnMount": refetchOnMount, - "retryOnMount": retryOnMount, - "onSuccess": onSuccess, - "onError": onError, - "onSettled": onSettled, - "useErrorBoundary": useErrorBoundary, - "select": select, - "suspense": suspense, - "keepPreviousData": keepPreviousData, - "placeholderData": placeholderData, - "optimisticResults": optimisticResults, - }; - } -} - -class QueryFunctionContext { - QueryKey queryKey; - /* AbortSignal */ dynamic? signal; - TPageParam? pageParam; - QueryMeta? meta; - - QueryFunctionContext({ - required this.queryKey, - this.signal, - this.pageParam, - this.meta, - }); -} - -class DefaultOptions { - QueryObserverOptions? queries; - // MutationObserverOptions? mutations; - DefaultOptions({ - this.queries, - }); -} - -class FetchQueryOptions, TError, - TData extends Map> - extends QueryOptions { - /// The time after data is considered stale. - /// If the data is fresh it will be returned from the cache. - Duration? staleTime; - FetchQueryOptions({ - ShouldRetryFunction? retry, - RetryDelayFunction? retryDelay, - Duration? cacheTime, - bool Function(TData? oldData, TData newData)? isDataEqual, - QueryFunction? queryFn, - QueryKey? queryKey, - String? queryHash, - QueryKeyHashFunction? queryKeyHashFn, - TData? initialData, - DateTime? initialDataUpdatedAt, - QueryBehavior? behavior, - bool? structuralSharing, - GetPreviousPageParamFunction? getPreviousPageParam, - GetNextPageParamFunction? getNextPageParam, - bool? defaulted, - this.staleTime, - }) : super( - retry: retry, - retryDelay: retryDelay, - cacheTime: cacheTime, - isDataEqual: isDataEqual, - queryFn: queryFn, - queryKey: queryKey, - queryHash: queryHash, - queryKeyHashFn: queryKeyHashFn, - initialData: initialData, - initialDataUpdatedAt: initialDataUpdatedAt, - behavior: behavior, - structuralSharing: structuralSharing, - getPreviousPageParam: getPreviousPageParam, - getNextPageParam: getNextPageParam, - defaulted: defaulted, - ); - - FetchQueryOptions.fromJson(Map json) - : staleTime = json["staleTime"], - super.fromJson(json); - - @override - Map toJson() { - return { - ...super.toJson(), - "staleTime": staleTime, - }; - } -} diff --git a/packages/fl_query/lib/src/core/notify_manager.dart b/packages/fl_query/lib/src/core/notify_manager.dart deleted file mode 100644 index 8acdc71..0000000 --- a/packages/fl_query/lib/src/core/notify_manager.dart +++ /dev/null @@ -1,95 +0,0 @@ -// TYPES - -import 'dart:async'; - -typedef NotifyCallback = void Function(); - -typedef NotifyFunction = void Function(void Function() callback); - -typedef BatchNotifyFunction = void Function(void Function() callback); - -class NotifyManager { - List _queue; - int _transactions; - late NotifyFunction _notifyFn; - late BatchNotifyFunction _batchNotifyFn; - - NotifyManager() - : _queue = [], - _transactions = 0 { - _notifyFn = (void Function() callback) { - callback(); - }; - - _batchNotifyFn = (void Function() callback) { - callback(); - }; - } - - T batch(T Function() callback) { - final T result; - _transactions++; - try { - result = callback(); - } finally { - _transactions--; - if (_transactions == 0) { - flush(); - } - } - return result; - } - - void schedule(NotifyCallback callback) { - if (_transactions > 0) { - _queue.add(callback); - } else { - scheduleMicrotask(() { - _notifyFn(callback); - }); - } - } - - /// All calls to the wrapped function will be batched. - T batchCalls(T callback) { - void fn(List? args) { - schedule(() { - callback(args); - }); - } - - ; - return fn as T; - } - - void flush() { - var queue = _queue; - _queue = []; - if (queue.isNotEmpty) { - scheduleMicrotask(() { - _batchNotifyFn(() { - queue.forEach((fn) { - _notifyFn(fn); - }); - }); - }); - } - } - - ///Use this method to set a custom notify function. - void setNotifyFunction(NotifyFunction fn) { - _notifyFn = fn; - } - - /// Use this method to set a custom function to batch notifications - /// together into a single tick. - /// By default React Query will use the batch function provided by - /// ReactDOM or React Native. - void setBatchNotifyFunction(BatchNotifyFunction fn) { - _batchNotifyFn = fn; - } -} - -// SINGLETON - -NotifyManager notifyManager = new NotifyManager(); diff --git a/packages/fl_query/lib/src/core/online_manager.dart b/packages/fl_query/lib/src/core/online_manager.dart deleted file mode 100644 index cfc15ea..0000000 --- a/packages/fl_query/lib/src/core/online_manager.dart +++ /dev/null @@ -1,72 +0,0 @@ -import 'package:fl_query/src/core/subscribable.dart'; -import 'package:internet_connection_checker/internet_connection_checker.dart'; - -typedef SetupFn = void Function()? Function( - void Function([bool? online]) setOnline); - -class OnlineManager extends Subscribable { - bool? _online; - void Function()? _cleanup; - SetupFn? _setup; - - OnlineManager([InternetConnectionChecker? connectionChecker]) { - connectionChecker ??= InternetConnectionChecker(); - _setup = (listener) { - var subscription = - connectionChecker!.onStatusChange.listen((status) => listener()); - return () { - subscription.cancel(); - }; - }; - } - - @override - void onSubscribe() { - if (_cleanup == null) { - setEventListener(_setup!); - } - } - - @override - void onUnsubscribe() { - if (!hasListeners()) { - _cleanup?.call(); - _cleanup = null; - } - } - - void setEventListener(SetupFn setup) { - _setup = setup; - _cleanup?.call(); - _cleanup = setup(([bool? online]) { - if (online != null) { - setOnline(online); - } else { - onOnline(); - } - }); - } - - void setOnline(bool? online) { - _online = online; - if (online != null && online) { - onOnline(); - } - } - - void onOnline() { - listeners.forEach((listener) { - listener(); - }); - } - - Future isOnline() { - if (_online != null) { - return Future.value(_online!); - } - - return InternetConnectionChecker().hasConnection; - } -} - -OnlineManager onlineManager = OnlineManager(); diff --git a/packages/fl_query/lib/src/core/query.dart b/packages/fl_query/lib/src/core/query.dart deleted file mode 100644 index 8223f23..0000000 --- a/packages/fl_query/lib/src/core/query.dart +++ /dev/null @@ -1,666 +0,0 @@ -import 'dart:async'; -import 'dart:collection'; -import 'dart:convert'; -import 'dart:math'; - -import 'package:fl_query/src/core/models.dart'; -import 'package:fl_query/src/core/notify_manager.dart'; -import 'package:fl_query/src/core/query_cache.dart'; -import 'package:fl_query/src/core/query_key.dart'; -import 'package:fl_query/src/core/query_observer.dart'; -import 'package:fl_query/src/core/retryer.dart'; -import 'package:fl_query/src/core/utils.dart'; -import 'package:meta/meta.dart'; -import 'package:collection/collection.dart'; - -class FetchOptions { - bool? cancelRefetch; - dynamic meta; - FetchOptions({this.cancelRefetch, this.meta}); -} - -class FetchContext, TError, - TData extends Map> { - FutureOr Function() fetchFn; - FetchOptions? fetchOptions; - QueryOptions options; - QueryKey queryKey; - QueryState state; - QueryMeta? meta; - - FetchContext({ - required this.fetchFn, - required this.options, - required this.queryKey, - required this.state, - this.meta, - this.fetchOptions, - }); -} - -class QueryBehavior, TError, - TData extends Map> { - void Function(FetchContext context) onFetch; - QueryBehavior({required this.onFetch}); -} - -class QueryState, TError> { - TData? data; - TError? error; - QueryStatus status; - DateTime? dataUpdatedAt; - int dataUpdateCount; - DateTime? errorUpdatedAt; - int errorUpdateCount; - int fetchFailureCount; - dynamic fetchMeta; - bool isFetching; - bool isInvalidated; - bool isPaused; - - QueryState({ - required this.status, - required this.dataUpdatedAt, - required this.dataUpdateCount, - required this.errorUpdatedAt, - required this.errorUpdateCount, - required this.fetchFailureCount, - required this.fetchMeta, - required this.isFetching, - required this.isInvalidated, - required this.isPaused, - this.data, - this.error, - }); - - QueryState.fromJson(Map json) - : data = json["data"], - error = json["error"], - status = json["status"], - dataUpdatedAt = json["dataUpdatedAt"], - dataUpdateCount = json["dataUpdateCount"], - errorUpdatedAt = json["errorUpdatedAt"], - errorUpdateCount = json["errorUpdateCount"], - fetchFailureCount = json["fetchFailureCount"], - fetchMeta = json["fetchMeta"], - isFetching = json["isFetching"], - isInvalidated = json["isInvalidated"], - isPaused = json["isPaused"]; - - Map toJson() { - return { - "data": data, - "error": error, - "status": status, - "dataUpdatedAt": dataUpdatedAt, - "dataUpdateCount": dataUpdateCount, - "errorUpdatedAt": errorUpdatedAt, - "errorUpdateCount": errorUpdateCount, - "fetchFailureCount": fetchFailureCount, - "fetchMeta": fetchMeta, - "isFetching": isFetching, - "isInvalidated": isInvalidated, - "isPaused": isPaused, - }; - } -} - -enum ActionType { - failed, - fetch, - success, - error, - invalidate, - pause, - resume, - setState, -} - -class SetStateOptions { - Object? meta; - SetStateOptions({this.meta}); - Map toJson() { - return {"meta": meta}; - } -} - -class Action, TError> { - ActionType type; - Object? meta; - TData? data; - DateTime? dataUpdatedAt; - TError? error; - QueryState? state; - SetStateOptions? setStateOptions; - - Action( - this.type, { - this.meta, - this.data, - this.dataUpdatedAt, - this.error, - this.state, - this.setStateOptions, - }) { - if (type == ActionType.error && error == null) - throw Exception( - "[Action.Action] property `error` can't be null when `type` = `$type`"); - - if (type == ActionType.setState && state == null) - throw Exception( - "[Action.Action] property `state` can't be null when `type` = `$type`"); - } - - Map toJson() { - return { - "type": type, - "meta": meta, - "data": data, - "dataUpdatedAt": dataUpdatedAt, - "error": error, - "state": state, - "setStateOptions": setStateOptions, - }; - } -} - -class Query, TError, - TData extends Map> { - QueryKey queryKey; - String queryHash; - late QueryOptions options; - late QueryState initialState; - QueryState? revertState; - late QueryState state; - Duration? cacheTime; - QueryMeta? meta; - - QueryCache _cache; - // Future? _future; - Completer? _completer; - Timer? _gcTimeout; - Retryer? _retryer; - List _observers; - QueryOptions? _defaultOptions; - bool _abortSignalConsumed; - bool _hadObservers; - - Query({ - required this.queryKey, - required this.queryHash, - required QueryCache cache, - QueryOptions? options, - QueryOptions? defaultOptions, - QueryState? state, - QueryMeta? meta, - }) : _abortSignalConsumed = false, - _hadObservers = false, - _defaultOptions = defaultOptions, - _observers = [], - _cache = cache { - _setOptions(options); - initialState = state ?? _getDefaultState(this.options); - this.state = initialState; - this.meta = meta; - _scheduleGc(); - } - - void _scheduleGc() { - this._clearGcTimeout(); - if (this.cacheTime != null) { - _gcTimeout = Timer(cacheTime!, () { - this._optionalRemove(); - }); - } - } - - void _clearGcTimeout() { - _gcTimeout?.cancel(); - _gcTimeout = null; - } - - void _optionalRemove() { - if (_observers.isEmpty) { - if (state.isFetching) { - if (_hadObservers) { - _scheduleGc(); - } - } else { - _cache.remove(this); - } - } - } - - void _setOptions(QueryOptions? options) { - this.options = QueryOptions.fromJson({ - ...(_defaultOptions?.toJson() ?? {}), - ...(options?.toJson() ?? {}), - }); - meta = options?.meta; - - /// Default to [5 minutes] if cache time isn't set - cacheTime = Duration( - milliseconds: max( - cacheTime?.inMilliseconds ?? 0, - this.options.cacheTime?.inMilliseconds ?? 5 * 60 * 1000, - )); - } - - QueryState _getDefaultState( - QueryOptions options) { - var data = options.initialData; - bool hasData = data != null; - - DateTime? initialDataUpdatedAt = - hasData ? options.initialDataUpdatedAt : null; - - return QueryState( - data: data, - dataUpdateCount: 0, - dataUpdatedAt: hasData ? initialDataUpdatedAt ?? DateTime.now() : null, - error: null, - errorUpdateCount: 0, - errorUpdatedAt: null, - fetchFailureCount: 0, - fetchMeta: null, - isFetching: false, - isInvalidated: false, - isPaused: false, - status: hasData ? QueryStatus.success : QueryStatus.idle, - ); - } - - TData setData( - DataUpdateFunction updater, { - DateTime? updatedAt, - }) { - try { - var prevData = this.state.data; - var data = updater(prevData); - // Use prev data if an isDataEqual function is defined and returns `true` - if (this.options.isDataEqual?.call(prevData, data) == true) { - data = prevData as TData; - } else if (this.options.structuralSharing != false) { - // Structurally share data between prev and new data if needed - final merged = - Map.from(replaceEqualDeep(prevData, data)); - data = merged as TData; - } - // Set data and mark it as cached - _dispatch(Action( - ActionType.success, - data: data, - dataUpdatedAt: updatedAt, - )); - return data; - } catch (e, stack) { - print("[Query.setData] $e"); - print(stack); - rethrow; - } - } - - void setState( - QueryState state, [ - SetStateOptions? setStateOptions, - ]) { - _dispatch(Action( - ActionType.setState, - state: state, - setStateOptions: setStateOptions, - )); - } - - Future cancel({bool? revert, bool? silent}) { - // var future = _future; - _retryer?.cancel(revert: revert, silent: silent); - if (_completer != null && !_completer!.isCompleted) { - _completer!.completeError("Cancelled Job", StackTrace.current); - return _completer!.future.then(noop).catchError(noop); - } - return Future.value(); - } - - void reset() { - destroy(); - setState(initialState); - } - - destroy() { - _clearGcTimeout(); - cancel(silent: true); - } - - bool isActive() { - return _observers.any((observer) => observer.options.enabled != false); - } - - bool isFetching() { - return this.state.isFetching; - } - - Future fetch([ - QueryOptions? options, - ObserverFetchOptions? fetchOptions, - ]) { - if (this.state.isFetching) { - if (this.state.dataUpdatedAt != null && - fetchOptions?.cancelRefetch == true) { - // Silently cancel current fetch if the user wants to cancel re-fetches - this.cancel(silent: true); - } else if (_completer != null) { - // make sure that retries that were potentially cancelled due to unmounts can continue - _retryer?.continueRetry(); - // Return current promise if we are already fetching - return _completer!.future; - } - } - - // Update config if passed, otherwise the config from the last execution is used - if (options != null) { - _setOptions(options); - } - - // Use the options from the first observer with a query function if no function is found. - // This can happen when the query is hydrated or created with setQueryData. - if (this.options.queryFn == null) { - final observer = - _observers.firstWhereOrNull((x) => x.options.queryFn != null); - if (observer != null) { - _setOptions(QueryOptions( - queryKey: observer.options.queryKey, - queryKeyHashFn: observer.options.queryKeyHashFn, - cacheTime: observer.options.cacheTime, - isDataEqual: observer.options.isDataEqual, - queryFn: - observer.options.queryFn as QueryFunction, - queryHash: observer.options.queryHash, - initialData: observer.options.initialData as TData?, - initialDataUpdatedAt: observer.options.initialDataUpdatedAt, - meta: observer.options.meta, - structuralSharing: observer.options.structuralSharing, - defaulted: observer.options.defaulted, - )); - } - } - - QueryFunctionContext queryFnContext = QueryFunctionContext( - queryKey: queryKey, - meta: meta, - ); - - /// !!LANGUAGE LIMITATION!! There's no equivalent of [AbortController] - /// the [get] can be implemented using Dart's getter but it'd be - /// useless since there's no equivalent of AbortController. - /// Have to find a better way to control ABORTION - - // Object.defineProperty(queryFnContext, 'signal', { - // enumerable: true, - // get: () { - // if (abortController) { - // this.abortSignalConsumed = true - // return abortController.signal - // } - // return undefined - // }, - // }) - - // Create fetch function - FutureOr fetchFn() { - if (this.options.queryFn == null) { - return Future.error('Missing queryFn'); - } - _abortSignalConsumed = false; - return options!.queryFn!.call(queryFnContext); - } - - // Trigger behavior hook - FetchContext context = - FetchContext( - fetchOptions: fetchOptions, - options: this.options, - queryKey: queryKey, - state: this.state, - fetchFn: fetchFn, - meta: this.meta, - ); - - this.options.behavior?.onFetch(context); - // Store state in case the current fetch needs to be reverted - this.revertState = this.state; - - // Set to fetching state if not already in it - if (!this.state.isFetching || - this.state.fetchMeta != context.fetchOptions?.meta) { - _dispatch(Action(ActionType.fetch, meta: context.fetchOptions?.meta)); - } - - _retryer = Retryer( - fn: context.fetchFn as FutureOr Function(), - // abort: abortController?.abort?.bind(abortController), - onSuccess: (data) { - this.setData((_) => data); - - // Notify cache callback - _cache.onData?.call(data, this); - if (_completer?.isCompleted == false) _completer?.complete(data); - // Remove query after fetching if cache time is 0 - if (this.cacheTime == null || this.cacheTime == Duration.zero) { - _optionalRemove(); - } - }, - onError: (TError error) { - // Optimistically update state if needed - if (!(isCancelledError(error) && (error as dynamic)?.silent == true)) { - _dispatch(Action(ActionType.error, error: error)); - } - - if (!isCancelledError(error)) { - // Notify cache callback - _cache.onError?.call(error, this); - - // Log error - // getLogger().error(error); - } - - // Remove query after fetching if cache time is 0 - if (this.cacheTime == null || this.cacheTime == Duration.zero) { - _optionalRemove(); - } - if (_completer?.isCompleted == false) - _completer?.completeError( - error ?? "Retry Failed", StackTrace.current); - }, - onFail: (failureCount, error) { - _dispatch(Action(ActionType.failed)); - }, - onPause: () { - _dispatch(Action(ActionType.pause)); - }, - onContinue: () { - _dispatch(Action(ActionType.resume)); - }, - retry: context.options.retry, - retryDelay: context.options.retryDelay, - ); - - this._completer = _retryer!.completer; - return this._completer!.future; - } - - void _dispatch(Action action) { - this.state = this.reducer(this.state, action); - - notifyManager.batch(() { - _observers.forEach((observer) { - observer.onQueryUpdate(action); - }); - _cache.notify(QueryCacheNotifyEvent( - QueryCacheNotifyEventType.queryUpdated, - this, - action: action, - )); - }); - } - - void addObserver(QueryObserver observer) { - if (_observers.indexOf(observer) == -1) { - _observers.add(observer); - _hadObservers = true; - - // Stop the query from being garbage collected - _clearGcTimeout(); - - _cache.notify(QueryCacheNotifyEvent( - QueryCacheNotifyEventType.observerAdded, - this, - observer: observer, - )); - } - } - - void removeObserver(QueryObserver observer) { - if (_observers.indexOf(observer) != -1) { - _observers = _observers.where((x) => x != observer).toList(); - - if (_observers.isEmpty) { - // If the transport layer does not support cancellation - // we'll let the query continue so the result can be cached - if (_retryer != null) { - if (_retryer?.isTransportCancelable == true || _abortSignalConsumed) { - _retryer?.cancel(revert: true); - } else { - _retryer?.cancelRetry(); - } - } - - if (cacheTime != null) { - _scheduleGc(); - } else { - _cache.remove(this); - } - } - - _cache.notify(QueryCacheNotifyEvent( - QueryCacheNotifyEventType.observerRemoved, - this, - observer: observer, - )); - } - } - - int getObserversCount() { - return _observers.length; - } - - void invalidate() { - if (!this.state.isInvalidated) { - _dispatch(Action(ActionType.invalidate)); - } - } - - bool isStale() { - return (this.state.isInvalidated || - this.state.dataUpdatedAt == null || - _observers - .any((observer) => observer.getCurrentResult()?.isStale == true)); - } - - bool isStaleByTime(Duration? staleTime) { - return (this.state.isInvalidated || - this.state.dataUpdatedAt == null || - timeUntilStale(this.state.dataUpdatedAt!, staleTime) == Duration.zero); - } - - void onOnline() { - var observer = _observers - .firstWhereOrNull((x) => x.shouldFetchCurrentQueryOnReconnect()); - - if (observer != null) { - observer.refetch(); - } - - // Continue fetch if currently paused - _retryer?.continueFn(); - } - - @protected - QueryState reducer( - QueryState state, - Action action, - ) { - switch (action.type) { - case ActionType.failed: - return QueryState.fromJson({ - ...state.toJson(), - "fetchFailureCount": state.fetchFailureCount + 1, - }); - case ActionType.fetch: - return QueryState.fromJson({ - ...state.toJson(), - "fetchFailureCount": 0, - "fetchMeta": action.meta, - "isFetching": true, - "isPaused": false, - if (state.dataUpdatedAt == null) - ...({ - "error": null, - "status": QueryStatus.loading, - }) - }); - case ActionType.success: - return QueryState.fromJson({ - ...state.toJson(), - "data": action.data, - "dataUpdateCount": state.dataUpdateCount + 1, - "dataUpdatedAt": action.dataUpdatedAt ?? DateTime.now(), - "error": null, - "fetchFailureCount": 0, - "isFetching": false, - "isInvalidated": false, - "isPaused": false, - "status": QueryStatus.success, - }); - case ActionType.error: - var error = action.error as dynamic; - if (isCancelledError(error) && - error?.revert == true && - revertState != null) { - return QueryState.fromJson(revertState!.toJson()); - } - - return QueryState.fromJson({ - ...state.toJson(), - "error": error as TError, - "errorUpdateCount": state.errorUpdateCount + 1, - "errorUpdatedAt": DateTime.now(), - "fetchFailureCount": state.fetchFailureCount + 1, - "isFetching": false, - "isPaused": false, - "status": QueryStatus.error, - }); - case ActionType.invalidate: - return QueryState.fromJson({ - ...state.toJson(), - "isInvalidated": true, - }); - case ActionType.pause: - return QueryState.fromJson({ - ...state.toJson(), - "isPaused": true, - }); - case ActionType.resume: - return QueryState.fromJson({ - ...state.toJson(), - "isPaused": false, - }); - case ActionType.setState: - return QueryState.fromJson({ - ...state.toJson(), - ...(action.state?.toJson() ?? {}), - }); - default: - return state; - } - } -} diff --git a/packages/fl_query/lib/src/core/query_cache.dart b/packages/fl_query/lib/src/core/query_cache.dart deleted file mode 100644 index 0775ce0..0000000 --- a/packages/fl_query/lib/src/core/query_cache.dart +++ /dev/null @@ -1,190 +0,0 @@ -import 'package:fl_query/src/core/models.dart'; -import 'package:fl_query/src/core/notify_manager.dart'; -import 'package:fl_query/src/core/query.dart'; -import 'package:fl_query/src/core/query_client.dart'; -import 'package:fl_query/src/core/query_key.dart'; -import 'package:fl_query/src/core/subscribable.dart'; -import 'package:fl_query/src/core/utils.dart'; -import 'package:collection/collection.dart'; - -enum QueryCacheNotifyEventType { - observerResultsUpdated, - observerRemoved, - observerAdded, - queryUpdated, - queryRemoved, - queryAdded -} - -class QueryCacheNotifyEvent { - Query query; - Object? observer; - Object? action; - QueryCacheNotifyEventType type; - QueryCacheNotifyEvent( - this.type, - this.query, { - this.observer, - this.action, - }) { - if ([ - QueryCacheNotifyEventType.observerAdded, - QueryCacheNotifyEventType.observerRemoved - ].contains(type) && - observer == null) - throw Exception( - "[QueryCacheNotifyEvent.constructor] property `observer` can't be `null` for `QueryCacheNotifyEventType.observerAdded` & `QueryCacheNotifyEventType.observerRemoved`"); - if (type == QueryCacheNotifyEventType.queryUpdated && action == null) - throw Exception( - "[QueryCacheNotifyEvent.constructor] property `action` can't be `null` for `QueryCacheNotifyEventType.queryUpdated`"); - } -} - -typedef QueryCacheListener = void Function(QueryCacheNotifyEvent? event); -typedef QueryCacheOnError = void Function(dynamic error, Query query); -typedef QueryCacheOnData = void Function(dynamic data, Query query); -typedef QueryHashMap = Map; - -class QueryCache extends Subscribable { - List _queries; - QueryHashMap _queriesMap; - - QueryCacheOnError? onError; - QueryCacheOnData? onData; - - QueryCache({ - this.onData, - this.onError, - }) : _queries = [], - _queriesMap = {}, - super(); - - Query build< - TQueryFnData extends Map, - TError, - TData extends Map>( - QueryClient client, - QueryOptions options, [ - QueryState? state, - ]) { - QueryKey queryKey = options.queryKey!; - String queryHash = - options.queryHash ?? hashQueryKeyByOptions(queryKey, options); - Query? query = - get(queryHash); - - if (query == null) { - query = Query( - cache: this, - queryKey: queryKey, - queryHash: queryHash, - options: client.defaultQueryOptions( - QueryObserverOptions.fromJson(options.toJson()), - ), - state: state, - defaultOptions: QueryOptions.fromJson( - client.getQueryDefaults(queryKey)?.toJson() ?? {}, - ), - meta: options.meta, - ); - add(query); - } - return query; - } - - QueryHashMap get queriesMap => _queriesMap; - List get queries => _queries; - - void add(Query query) { - if (!_queriesMap.containsKey(query.queryHash)) { - _queriesMap[query.queryHash] = query; - _queries.add(query); - notify( - QueryCacheNotifyEvent( - QueryCacheNotifyEventType.queryAdded, - query, - ), - ); - } - } - - void remove(Query query) { - Query? queryInMap = _queriesMap[query.queryHash]; - if (queryInMap == null) return; - query.destroy(); - _queries = _queries.where((x) => x != query).toList(); - if (queryInMap == query) { - _queriesMap.remove(query.queryHash); - } - notify(QueryCacheNotifyEvent( - QueryCacheNotifyEventType.queryRemoved, - query, - )); - } - - void clear() { - notifyManager.batch(() { - for (var query in _queries) { - remove(query); - } - }); - } - - Query? get< - TQueryFnData extends Map, - TError, - TData extends Map>(String queryHash) { - return _queriesMap[queryHash] as Query?; - } - - List getAll() { - return _queries; - } - - Query? find< - TQueryFnData extends Map, - TError, - TData extends Map>(QueryKey queryKey, - [QueryFilters? queryFilters]) { - queryFilters ??= QueryFilters(); - queryFilters.exact ??= true; - return _queries.firstWhereOrNull((query) => matchQuery( - queryFilters!, - query, - queryKey, - )) as Query?; - } - - List findAll([QueryKey? queryKeys, QueryFilters? filters]) { - return filters == null && queryKeys == null - ? _queries - : _queries - .where( - (query) => matchQuery( - filters ?? QueryFilters(), - query, - queryKeys, - ), - ) - .toList(); - } - - void notify(QueryCacheNotifyEvent event) { - notifyManager.batch(() { - for (final listener in listeners) { - listener(event); - } - }); - } - - /// Dummy function just to keep the API similar to react-query - void onFocus() {} - - void onOnline() { - notifyManager.batch(() { - _queries.forEach((query) { - query.onOnline(); - }); - }); - } -} diff --git a/packages/fl_query/lib/src/core/query_client.dart b/packages/fl_query/lib/src/core/query_client.dart deleted file mode 100644 index 3f8e47b..0000000 --- a/packages/fl_query/lib/src/core/query_client.dart +++ /dev/null @@ -1,390 +0,0 @@ -import 'package:fl_query/src/core/models.dart'; -import 'package:fl_query/src/core/notify_manager.dart'; -import 'package:fl_query/src/core/online_manager.dart'; -import 'package:fl_query/src/core/query.dart'; -import 'package:fl_query/src/core/query_cache.dart'; -import 'package:fl_query/src/core/query_key.dart'; -import 'package:fl_query/src/core/query_observer.dart'; -import 'package:fl_query/src/core/utils.dart'; -import 'package:collection/collection.dart'; - -class QueryDefaults { - QueryKey queryKey; - QueryOptions defaultOptions; - QueryDefaults({ - required this.queryKey, - required this.defaultOptions, - }); -} - -class MutationDefaults { - // QueryKey queryKey; - // QueryOptions defaultOptions; - // MutationDefaults({ - // required this.queryKey, - // required this.defaultOptions, - // }); -} - -class QueryData> { - QueryKey queryKey; - TData data; - QueryData({ - required this.queryKey, - required this.data, - }); -} - -class QueryClient { - QueryCache _queryCache; - // QueryCache _mutationCache; - DefaultOptions _defaultOptions; - List _queryDefaults; - // List _mutationDefaults; - void Function()? _unsubscribeFocus; - void Function()? _unsubscribeOnline; - // MutationKey _mutationKey; - // MutationOptions _mutationDefaultOptions; - - QueryClient({ - QueryCache? queryCache, - QueryCache? mutationCache, - DefaultOptions? defaultOptions, - }) : _queryCache = queryCache ?? QueryCache(), - _defaultOptions = defaultOptions ?? DefaultOptions(), - _queryDefaults = []; - /* _mutationDefaults = [], */ - /* _mutationCache = mutationCache ?? QueryCache() */ - - void mount() { - // this.unsubscribeFocus = focusManager.subscribe(() => { - // if (focusManager.isFocused() && onlineManager.isOnline()) { - // this.mutationCache.onFocus() - // this.queryCache.onFocus() - // } - // }) - _unsubscribeOnline = onlineManager.subscribe(() async { - if (/* focusManager.isFocused() && */ await onlineManager.isOnline()) { - // _mutationCache.onOnline(); - _queryCache.onOnline(); - } - }); - } - - void unmount() { - _unsubscribeFocus?.call(); - _unsubscribeOnline?.call(); - } - - int isFetching({QueryKey? queryKey, QueryFilters? filters}) { - filters?.fetching = true; - return _queryCache.findAll(null, filters).length; - } - - // int isMutating([MutationFilters? filters]) { - // return _mutationCache.findAll({ ...filters, fetching: true }).length - // } - - TData? getQueryData>( - QueryKey queryKey, [ - QueryFilters? filters, - ]) { - return _queryCache - .find>( - queryKey, filters ?? QueryFilters()) - ?.state - .data as TData?; - } - - List> getQueriesData>({ - QueryKey? queryKeys, - QueryFilters? filters, - }) { - return getQueryCache().findAll(queryKeys, filters).map((query) { - return QueryData( - data: query.state.data as TData, - queryKey: query.queryKey, - ); - }).toList(); - } - - TData setQueryData>( - QueryKey queryKey, - DataUpdateFunction updater, [ - DateTime? updatedAt, - ]) { - final QueryOptions, dynamic, TData> defaultedOptions = - QueryOptions, dynamic, TData>.fromJson( - defaultQueryOptions, dynamic, TData, - Map>( - QueryObserverOptions, dynamic, TData, - Map>(queryKey: queryKey)) - .toJson()); - return _queryCache - .build, dynamic, TData>(this, defaultedOptions) - .setData( - updater, - updatedAt: updatedAt, - ); - } - - List setQueriesData>({ - required DataUpdateFunction updater, - QueryKey? queryKeys, - QueryFilters? filters, - DateTime? updatedAt, - }) { - if (queryKeys == null && filters == null) - throw Exception( - "[QueryClient.setQueriesData] both `queryKey` & `filters` can't be null at the same time"); - return notifyManager - .batch(() => getQueryCache().findAll(queryKeys, filters).map( - (query) => QueryData( - queryKey: query.queryKey, - data: setQueryData( - query.queryKey, - updater, - updatedAt, - ), - ), - )) - .toList(); - } - - QueryState? - getQueryState, TError>( - QueryKey queryKey, [ - QueryFilters? filters, - ]) { - return _queryCache - .find>( - queryKey, - filters ?? QueryFilters(), - ) - ?.state as QueryState?; - } - - void removeQueries({QueryKey? queryKeys, QueryFilters? filters}) { - notifyManager.batch( - () => { - _queryCache.findAll(queryKeys, filters).forEach((query) { - _queryCache.remove(query); - }) - }, - ); - } - - Future resetQueries({ - QueryKey? queryKeys, - RefetchableQueryFilters? filters, - bool? throwOnError, - }) { - filters?.active = true; - var refetchFilters = RefetchableQueryFilters.fromJson({ - ...(filters?.toJson() ?? {}), - "active": true, - }); - - return notifyManager.batch(() { - _queryCache.findAll(queryKeys, filters).forEach((query) { - query.reset(); - }); - return refetchQueries( - filters: refetchFilters, - options: RefetchOptions(throwOnError: throwOnError), - ); - }); - } - - Future cancelQueries({ - QueryKey? queryKeys, - QueryFilters? filters, - bool? revert = true, - bool? silent, - }) { - var futures = notifyManager.batch(() => - _queryCache.findAll(queryKeys, filters).map((query) => query.cancel( - revert: revert, - silent: silent, - ))); - return Future.wait(futures).then(noop).catchError(noop); - } - - Future invalidateQueries({ - QueryKey? queryKeys, - InvalidateQueryFilters? filters, - RefetchOptions? options, - }) { - var refetchFilters = RefetchableQueryFilters.fromJson({ - ...(filters?.toJson() ?? {}), - // if filters.refetchActive is not provided and filters.active is explicitly false, - // e.g. invalidateQueries({ active: false }), we don't want to refetch active queries - "active": filters?.refetchActive ?? filters?.active ?? true, - "inactive": filters?.refetchInactive ?? false, - }); - return notifyManager.batch(() { - _queryCache.findAll(queryKeys, filters).forEach((query) { - query.invalidate(); - }); - return this.refetchQueries( - filters: refetchFilters, - options: options, - ); - }); - } - - Future refetchQueries({ - QueryKey? queryKeys, - RefetchableQueryFilters? filters, - RefetchOptions? options, - }) { - var futures = notifyManager.batch( - () => _queryCache.findAll(queryKeys, filters).map( - (query) => query.fetch( - null, - ObserverFetchOptions( - cancelRefetch: options?.cancelRefetch, - throwOnError: options?.throwOnError, - meta: {"refetchPage": filters?.refetchPage}, - ), - ), - ), - ); - - var future = Future.wait(futures).then(noop); - - if (options?.throwOnError == false) { - future = future.catchError(noop); - } - - return future; - } - - Future fetchQuery, TError, - TData extends Map>({ - QueryKey? queryKey, - QueryFunction? queryFn, - FetchQueryOptions? options, - }) { - final defaultedOptions = this.defaultQueryOptions( - QueryObserverOptions, dynamic, Map, - TData>( - queryFn: queryFn, - queryKey: queryKey, - staleTime: options?.staleTime, - cacheTime: options?.cacheTime, - defaulted: options?.defaulted, - initialData: options?.initialData, - initialDataUpdatedAt: options?.initialDataUpdatedAt, - isDataEqual: options?.isDataEqual, - meta: options?.meta, - queryHash: options?.queryHash, - queryKeyHashFn: options?.queryKeyHashFn, - structuralSharing: options?.structuralSharing, - ), - ); - // returning 0 indicates turing off retry - defaultedOptions.retry ??= (_, __) => 0; - final query = _queryCache.build, dynamic, TData>( - this, defaultedOptions); - return query.isStaleByTime(defaultedOptions.staleTime) - ? query.fetch(defaultedOptions) - : Future.value(query.state.data as TData); - } - - Future prefetchQuery, TError, - TData extends Map>({ - QueryKey? queryKey, - QueryFunction? queryFn, - FetchQueryOptions? options, - }) { - return fetchQuery( - queryKey: queryKey, - queryFn: queryFn, - options: options, - ).then(noop).catchError(noop); - } - - QueryObserverOptions defaultQueryOptions< - TQueryFnData extends Map, - TError, - TData extends Map, - TQueryData extends Map>( - QueryObserverOptions? options) { - if (options?.defaulted == true) return options!; - final defaultedOptions = - QueryObserverOptions.fromJson({ - ...(_defaultOptions.queries?.toJson() ?? {}), - ...(getQueryDefaults(options?.queryKey)?.toJson() ?? {}), - ...(options?.toJson() ?? {}), - "defaulted": true, - }); - if (defaultedOptions.queryHash == null && - defaultedOptions.queryKey != null) { - defaultedOptions.queryHash = hashQueryKeyByOptions( - defaultedOptions.queryKey!, - defaultedOptions, - ); - } - - return defaultedOptions; - } - - QueryObserverOptions - defaultQueryObserverOptions< - TQueryFnData extends Map, - TError, - TData extends Map, - TQueryData extends Map>([ - QueryObserverOptions? options, - ]) { - return this.defaultQueryOptions(options); - } - - DefaultOptions getDefaultOptions() { - return _defaultOptions; - } - - void setDefaultOptions(DefaultOptions options) { - _defaultOptions = options; - } - - QueryObserverOptions? getQueryDefaults([QueryKey? queryKey]) { - return queryKey != null - ? QueryObserverOptions.fromJson((_queryDefaults - .firstWhereOrNull( - (x) => queryKey.key == x.queryKey.key, - ) - ?.defaultOptions) - ?.toJson() ?? - {}) - : null; - } - - void setQueryDefaults(QueryKey queryKey, QueryObserverOptions options) { - var result = _queryDefaults.firstWhereOrNull( - (x) => queryKey.key == x.queryKey.key, - ); - - if (result != null) { - result.defaultOptions = options; - } else { - _queryDefaults - .add(QueryDefaults(queryKey: queryKey, defaultOptions: options)); - } - } - - // getMutationDefaults() {} - // setMutationDefaults() {} - // getMutationCache() {} - - QueryCache getQueryCache() { - return _queryCache; - } - - void clear() { - _queryCache.clear(); - // _mutationCache.clear(); - } -} diff --git a/packages/fl_query/lib/src/core/query_key.dart b/packages/fl_query/lib/src/core/query_key.dart deleted file mode 100644 index 4e70647..0000000 --- a/packages/fl_query/lib/src/core/query_key.dart +++ /dev/null @@ -1,18 +0,0 @@ -/// Used for defining a unique identifier for a specific query -/// that can be used to read/modify/delete the query from the -/// store -class QueryKey { - List _key; - QueryKey(String key) : _key = [key]; - - QueryKey.fromList(List key) : _key = key; - QueryKey.parse(String keyStr) : _key = keyStr.split("."); - - String get key => _key.map((k) => k.replaceAll(".", "")).join("."); - List get keyAsList => _key; - - @override - String toString() { - return 'QueryKey("$key")'; - } -} diff --git a/packages/fl_query/lib/src/core/query_observer.dart b/packages/fl_query/lib/src/core/query_observer.dart deleted file mode 100644 index ac96106..0000000 --- a/packages/fl_query/lib/src/core/query_observer.dart +++ /dev/null @@ -1,740 +0,0 @@ -/// `TQueryData`, `TQueryFnData`, `TData` should be [Map]s for shallow/deep equality checks -/// Or these can be data classes that have `toJson` method & `fromJson` -/// constructor. This also requires the data-class to be passed to the -/// [Query] constructor parameters e.g ([dataType]) - -import 'dart:async'; - -import 'package:fl_query/src/core/models.dart'; -import 'package:fl_query/src/core/notify_manager.dart'; -import 'package:fl_query/src/core/query.dart'; -import 'package:fl_query/src/core/query_cache.dart'; -import 'package:fl_query/src/core/query_client.dart'; -import 'package:fl_query/src/core/retryer.dart'; -import 'package:fl_query/src/core/subscribable.dart'; -import 'package:fl_query/src/core/utils.dart'; -import 'package:meta/meta.dart'; - -typedef QueryObserverListener, TError> = void - Function(QueryObserverResult result); - -class NotifyOptions { - bool? cache; - bool? listeners; - bool? onError; - bool? onSuccess; - - NotifyOptions({this.cache, this.listeners, this.onError, this.onSuccess}); - - /// [safe] default `true`- if it's true then there'll be no key - /// containing null value - Map toJson([bool safe = true]) { - final Map data = new Map(); - if (safe) { - if (this.cache != null) data['cache'] = this.cache; - if (this.listeners != null) data['listeners'] = this.listeners; - if (this.onError != null) data['onError'] = this.onError; - if (this.onSuccess != null) data['onSuccess'] = this.onSuccess; - } else { - data['cache'] = this.cache; - data['listeners'] = this.listeners; - data['onError'] = this.onError; - data['onSuccess'] = this.onSuccess; - } - return data; - } - - NotifyOptions.fromJson(Map json) { - cache = json['cache']; - listeners = json['listeners']; - onError = json['onError']; - onSuccess = json['onSuccess']; - } -} - -class ObserverFetchOptions extends FetchOptions { - bool? throwOnError; - ObserverFetchOptions({ - this.throwOnError, - bool? cancelRefetch, - dynamic meta, - }) : super(cancelRefetch: cancelRefetch, meta: meta); -} - -class SelectQuery, - TData extends Map> { - TData Function(TQueryData data) fn; - TData result; - SelectQuery(this.fn, this.result); -} - -class QueryObserver< - TQueryFnData extends Map, - TError, - TData extends Map, - TQueryData extends Map> - extends Subscribable { - QueryObserverOptions options; - QueryClient _client; - Query? _currentQuery; - - late QueryState _currentQueryInitialState; - QueryObserverResult? _currentResult; - - /// List of tracked keys/properties of [QueryObserverResult] - late List _trackedProps; - - QueryState? _currentResultState; - QueryObserverOptions? - _currentResultOptions; - QueryObserverResult? _previousQueryResult; - Exception? _previousSelectError; - SelectQuery? _previousSelect; - Timer? _staleTimeout; - Timer? _refetchInterval; - Duration? _currentRefetchInterval; - - @protected - Timer? get refetchInterval => _refetchInterval; - - QueryObserver( - this._client, - QueryObserverOptions? _options, - ) : _trackedProps = [], - _previousSelectError = null, - options = _options ?? QueryObserverOptions(), - super() { - this.setOptions(options); - } - - bool shouldFetchCurrentQueryOnReconnect() { - return shouldFetchOnReconnect(_currentQuery!, this.options); - } - - @override - void onSubscribe() { - if (listeners.length == 1) { - _currentQuery?.addObserver(this); - - if (_currentQuery != null && - shouldFetchOnMount(_currentQuery!, options)) { - _executeFetch(); - } - - _updateTimers(); - } - } - - @override - void onUnsubscribe() { - if (listeners.isEmpty) { - this.destroy(); - } - } - - void destroy() { - listeners = []; - _clearTimers(); - _currentQuery?.removeObserver(this); - } - - void setOptions( - QueryObserverOptions? options, [ - NotifyOptions? notifyOptions, - ]) { - final prevOptions = this.options; - final prevQuery = _currentQuery; - - this.options = this._client.defaultQueryObserverOptions(options); - - this.options.queryKey ??= prevOptions.queryKey; - - _updateQuery(); - - bool mounted = hasListeners(); - - if (mounted && - _currentQuery != null && - prevQuery != null && - shouldFetchOptionally( - _currentQuery!, prevQuery, this.options, prevOptions)) { - _executeFetch(); - } - ; - - this.updateResult(notifyOptions); - if (mounted && - (_currentQuery != prevQuery || - this.options.enabled != prevOptions.enabled || - this.options.staleTime != prevOptions.staleTime)) { - _updateStaleTimeout(); - } - - final nextRefetchInterval = _computeRefetchInterval(); - - // Update refetch interval if needed - if (mounted && - (_currentQuery != prevQuery || - this.options.enabled != prevOptions.enabled || - nextRefetchInterval != _currentRefetchInterval)) { - _updateRefetchInterval(nextRefetchInterval); - } - } - - QueryObserverResult getOptimisticResult( - QueryObserverOptions options, - ) { - final defaultedOptions = _client.defaultQueryObserverOptions(options); - - final query = _client.getQueryCache().build(_client, defaultedOptions); - - return createResult(query, defaultedOptions); - } - - QueryObserverResult? getCurrentResult() { - return _currentResult; - } - - /// There's nothing similar to JS [defineProperty] in dart native - /// objects thus modifying the underlying property `get` method is - /// impossible so [trackProp] can't be implemented at the moment - /// At least not following this procedure - QueryObserverResult trackResult( - QueryObserverResult result, - QueryObserverOptions - defaultedOptions, - ) { - // final Map trackedResult = {}; - // const trackProp = (key: keyof QueryObserverResult) => { - // if (!this.trackedProps.includes(key)) { - // this.trackedProps.push(key) - // } - // } - // Object.keys(result).forEach(key => { - // Object.defineProperty(trackedResult, key, { - // configurable: false, - // enumerable: true, - // get: () => { - // trackProp(key as keyof QueryObserverResult) - // return result[key as keyof QueryObserverResult] - // }, - // }) - // }) - // if (defaultedOptions.useErrorBoundary || defaultedOptions.suspense) { - // trackProp('error') - // } - // return trackedResult - - throw UnimplementedError("COULD NOT IMPLEMENT DUE TO LANGUAGE LIMITATIONS"); - } - - Future> getNextResult([ - bool? throwOnError, - ]) { - final completer = Completer>(); - var unsubscribe; - unsubscribe = subscribe((result) { - if (!result.isFetching) { - unsubscribe?.call(); - if (result.isError && throwOnError == true) { - if (!completer.isCompleted) completer.completeError(result.error); - } else { - if (!completer.isCompleted) - completer.complete( - result as QueryObserverResult, - ); - } - } - }); - return completer.future; - } - - Query getCurrentQuery() { - return _currentQuery!; - } - - Future> fetchOptimistic( - QueryObserverOptions options) { - final defaultedOptions = _client.defaultQueryObserverOptions(options); - final query = _client.getQueryCache().build(_client, defaultedOptions); - - return query.fetch().then((val) { - return createResult(query, defaultedOptions); - }); - } - - @protected - Future?> fetch( - ObserverFetchOptions fetchOptions, - ) { - return _executeFetch(fetchOptions).then((val) { - updateResult(); - return _currentResult; - }); - } - - Future _executeFetch([ObserverFetchOptions? fetchOptions]) { - // Make sure we reference the latest query as the current one might have been removed - _updateQuery(); - // Fetch - Future future = _currentQuery!.fetch( - this.options, - fetchOptions, - ); - - if (fetchOptions?.throwOnError != null) { - future = future.catchError((e) => e); - } - - return future; - } - - bool _shouldNotifyListeners(QueryObserverResult result, - [QueryObserverResult? prevResult]) { - if (prevResult == null) return true; - if (options.notifyOnChangeProps == false && - options.notifyOnChangePropsExclusions == null) { - return true; - } - - if (options.notifyOnChangeProps == 'tracked' && _trackedProps.isEmpty) { - return true; - } - - List? includedProps = options.notifyOnChangeProps == 'tracked' - ? _trackedProps - : options.notifyOnChangeProps; - - Map resultMap = result.toJson(); - Map prevResultMap = prevResult.toJson(); - - return resultMap.keys.any((key) { - final changed = resultMap[key] != prevResultMap[key]; - bool? isIncluded = includedProps?.any((x) => x == key); - bool isExcluded = - options.notifyOnChangePropsExclusions?.any((x) => x == key) ?? false; - return changed && - !isExcluded && - (includedProps == null || isIncluded == true); - }); - } - - void updateResult([NotifyOptions? notifyOptions]) { - final QueryObserverResult? prevResult = _currentResult; - - if (_currentQuery != null) - _currentResult = this.createResult(_currentQuery!, this.options); - _currentResultState = _currentQuery?.state; - _currentResultOptions = this.options; - - final isSameMap = - shallowEqualMap(_currentResult?.toJson(), prevResult?.toJson()); - // Only notify if something has changed - if (isSameMap) { - return; - } - NotifyOptions defaultNotifyOptions = NotifyOptions(cache: true); - if (notifyOptions?.listeners != false && - _currentResult != null && - _shouldNotifyListeners(_currentResult!, prevResult)) { - defaultNotifyOptions.listeners = true; - } - - final mergedNotifyOptions = { - ...defaultNotifyOptions.toJson(), - ...(notifyOptions?.toJson() ?? {}), - }; - - _notify(NotifyOptions.fromJson(mergedNotifyOptions)); - } - - void _updateQuery() { - final query = - this._client.getQueryCache().build(this._client, this.options); - - if (query == _currentQuery) return; - - final prevQuery = _currentQuery; - _currentQuery = query; - _currentQueryInitialState = query.state; - _previousQueryResult = _currentResult; - - if (hasListeners()) { - prevQuery?.removeObserver(this); - query.addObserver(this); - } - } - - void onQueryUpdate(Action action) { - final NotifyOptions notifyOptions = NotifyOptions(); - - if (action.type == 'success') { - notifyOptions.onSuccess = true; - } else if (action.type == 'error' && !isCancelledError(action.error)) { - notifyOptions.onError = true; - } - - updateResult(notifyOptions); - - if (this.hasListeners()) { - _updateTimers(); - } - } - - QueryObserverResult createResult( - Query query, - QueryObserverOptions options, - ) { - final prevQuery = _currentQuery; - final prevOptions = this.options; - final prevResult = _currentResult; - final prevResultState = _currentResultState; - final prevResultOptions = _currentResultOptions; - final bool queryChange = query != prevQuery; - final queryInitialState = - queryChange ? query.state : _currentQueryInitialState; - final prevQueryResult = queryChange ? _currentResult : _previousQueryResult; - - final state = query.state; - DateTime? dataUpdatedAt = state.dataUpdatedAt; - TError? error = state.error; - DateTime? errorUpdatedAt = state.errorUpdatedAt; - bool isFetching = state.isFetching; - QueryStatus status = state.status; - - bool isPreviousData = false; - bool isPlaceholderData = false; - TData? data; - - // Optimistically set result in fetching state if needed - if (options.optimisticResults == true) { - final bool mounted = hasListeners(); - - final bool fetchOnMount = !mounted && shouldFetchOnMount(query, options); - - bool fetchOptionally = mounted && - prevQuery != null && - shouldFetchOptionally(query, prevQuery, options, prevOptions); - - if (fetchOnMount || fetchOptionally) { - isFetching = true; - if (dataUpdatedAt == null) { - status = QueryStatus.loading; - } - } - } - - // Keep previous data if needed - if (prevQueryResult != null && - options.keepPreviousData == true && - state.dataUpdateCount == 0 && - prevQueryResult.isSuccess == true && - status != QueryStatus.error) { - data = prevQueryResult.data; - dataUpdatedAt = prevQueryResult.dataUpdatedAt; - status = prevQueryResult.status; - isPreviousData = true; - } - - // Select data if needed - else if (options.select != null && state.data != null) { - print("prevResult != null ${prevResult != null}"); - print( - "state.data == prevResultState?.data | ${state.data} == ${prevResultState?.data} | ${shallowEqualMap(state.data, prevResultState?.data)}"); - print( - "options.select == _previousSelect?.fn ${options.select == _previousSelect?.fn}"); - print("_previousSelectError == null ${_previousSelectError == null}"); - if (prevResult != null && - shallowEqualMap(state.data, prevResultState?.data) && - options.select == _previousSelect?.fn && - _previousSelectError == null) { - data = _previousSelect?.result; - } else { - try { - data = options.select?.call(state.data); - if (options.structuralSharing != false) { - data = Map.from( - replaceEqualDeep(prevResult?.data, data)) as TData; - } - if (options.select != null && data != null) { - _previousSelect = SelectQuery( - options.select!, - data, - ); - } - _previousSelectError = null; - } catch (selectError) { - // getLogger().error(selectError); - error = selectError as TError; - _previousSelectError = selectError as Exception; - errorUpdatedAt = DateTime.now(); - status = QueryStatus.error; - } - } - } - // Use query data - else { - data = state.data as TData?; - } - - if (options.placeholderData != null && - data == null && - (status == QueryStatus.loading || status == QueryStatus.idle)) { - var placeholderData; - - if (prevResult?.isPlaceholderData == true && - options.placeholderData == prevResultOptions?.placeholderData) { - placeholderData = prevResult?.data; - } else { - placeholderData = options.placeholderData; - if (options.select != null && placeholderData != null) { - try { - placeholderData = options.select?.call(placeholderData); - if (options.structuralSharing != false) { - placeholderData = - replaceEqualDeep(prevResult?.data, placeholderData); - } - _previousSelectError = null; - } catch (selectError) { - // getLogger().error(selectError); - error = selectError as TError; - _previousSelectError = selectError as Exception; - errorUpdatedAt = DateTime.now(); - status = QueryStatus.error; - } - } - } - - if (placeholderData != null) { - status = QueryStatus.success; - data = placeholderData as TData; - isPlaceholderData = true; - } - } - - final QueryObserverResult result = - QueryObserverResult( - status: status, - dataUpdatedAt: dataUpdatedAt, - isLoading: status == QueryStatus.loading, - isSuccess: status == QueryStatus.success, - isError: status == QueryStatus.error, - isIdle: status == QueryStatus.idle, - data: data, - error: error, - failureCount: state.fetchFailureCount, - isFetched: state.dataUpdateCount > 0 || state.errorUpdateCount > 0, - isFetchedAfterMount: - state.dataUpdateCount > queryInitialState.dataUpdateCount || - state.errorUpdateCount > queryInitialState.errorUpdateCount, - isFetching: isFetching, - isRefetching: isFetching && status != QueryStatus.loading, - isLoadingError: - status == QueryStatus.error && state.dataUpdatedAt == null, - isPlaceholderData: isPlaceholderData, - isPreviousData: isPreviousData, - isRefetchError: status == 'error' && state.dataUpdatedAt != 0, - isStale: isStale(query, options), - refetch: this.refetch, - remove: this.remove, - ); - return result; - } - - void _notify(NotifyOptions notifyOptions) { - notifyManager.batch(() { - // First trigger the configuration callbacks - if (notifyOptions.onSuccess == true && _currentResult != null) { - this.options.onSuccess?.call(_currentResult!.data!); - this.options.onSettled?.call(_currentResult!.data!); - } else if (notifyOptions.onError == true && _currentResult != null) { - this.options.onError?.call(_currentResult!.error!); - this.options.onSettled?.call(null, _currentResult!.error!); - } - - // Then trigger the listeners - if (notifyOptions.listeners == true && _currentResult != null) { - this.listeners.forEach((listener) { - listener(_currentResult!); - }); - } - - // Then the cache listeners - if (notifyOptions.cache == true && _currentQuery != null) { - _client.getQueryCache().notify( - QueryCacheNotifyEvent( - QueryCacheNotifyEventType.observerResultsUpdated, - _currentQuery as Query, - ), - ); - } - }); - } - - Duration? _computeRefetchInterval() { - return this.options.refetchInterval != null && _currentQuery != null - ? this.options.refetchInterval!(_currentResult?.data, _currentQuery!) - : null; - } - - void _updateTimers() { - _updateStaleTimeout(); - _updateRefetchInterval(_computeRefetchInterval()); - } - - void _updateStaleTimeout() { - _clearStaleTimeout(); - if (_currentResult?.isStale == true || - options.staleTime == null || - _currentResult?.dataUpdatedAt == null) return; - - // The timeout is sometimes triggered 1 ms before the stale time - // expiration. To mitigate this issue we always add 1 ms to the - // timeout. - Duration time = Duration( - milliseconds: - timeUntilStale(_currentResult!.dataUpdatedAt!, this.options.staleTime) - .inMilliseconds + - 1, - ); - - _staleTimeout = Timer(time, () { - if (!_currentResult!.isStale) { - this.updateResult(); - } - }); - } - - _updateRefetchInterval(Duration? nextInterval) { - _clearRefetchInterval(); - - _currentRefetchInterval = nextInterval; - - if (this.options.enabled == false || - _currentRefetchInterval == null || - _currentRefetchInterval == Duration.zero) return; - - _refetchInterval = Timer.periodic(_currentRefetchInterval!, (t) { - if (this.options.refetchIntervalInBackground == true) { - _executeFetch(); - } - }); - } - - void _clearTimers() { - _clearStaleTimeout(); - _clearRefetchInterval(); - } - - void _clearStaleTimeout() { - _staleTimeout?.cancel(); - _staleTimeout = null; - } - - void _clearRefetchInterval() { - _refetchInterval?.cancel(); - _refetchInterval = null; - } - - void remove() { - _client.getQueryCache().remove(_currentQuery as Query); - _clearTimers(); - _currentQuery?.removeObserver(this); - } - - Future?> refetch({ - RefetchableQueryFilters? filters, - RefetchOptions? options, - }) { - return fetch( - ObserverFetchOptions( - cancelRefetch: options?.cancelRefetch, - meta: filters?.toJson(), - throwOnError: options?.throwOnError, - ), - ); - } -} - -bool shouldLoadOnMount< - TQueryFnData extends Map, - TError, - TData extends Map, - TQueryData extends Map>( - Query query, - QueryObserverOptions options, -) { - return (options.enabled != false && - query.state.dataUpdatedAt == null && - !(query.state.status == QueryStatus.error && - options.retryOnMount == false)); -} - -bool shouldRefetchOnMount< - TQueryFnData extends Map, - TError, - TData extends Map, - TQueryData extends Map>( - Query query, - QueryObserverOptions options, -) { - return (options.enabled != false && - query.state.dataUpdatedAt != null && - (options.refetchOnMount == RefetchOnMount.always || - (options.refetchOnMount != RefetchOnMount.off && - isStale(query, options)))); -} - -bool shouldFetchOnMount< - TQueryFnData extends Map, - TError, - TData extends Map, - TQueryData extends Map>( - Query query, - QueryObserverOptions options, -) { - return (shouldLoadOnMount(query, options) || - shouldRefetchOnMount(query, options)); -} - -bool shouldFetchOnReconnect< - TQueryFnData extends Map, - TError, - TData extends Map, - TQueryData extends Map>( - Query query, - QueryObserverOptions options, -) { - return (options.enabled != false && - (options.refetchOnReconnect == RefetchOnReconnect.always || - (options.refetchOnReconnect != RefetchOnReconnect.off && - isStale( - query, options)))); -} - -bool shouldFetchOptionally< - TQueryFnData extends Map, - TError, - TData extends Map, - TQueryData extends Map>( - Query query, - Query prevQuery, - QueryObserverOptions options, - QueryObserverOptions prevOptions, -) { - return (options.enabled != false && - (query != prevQuery || prevOptions.enabled == false) && - (options.suspense != true || query.state.status != QueryStatus.error) && - isStale(query, options)); -} - -bool isStale< - TQueryFnData extends Map, - TError, - TData extends Map, - TQueryData extends Map>( - Query query, - QueryObserverOptions options, -) { - return query.isStaleByTime(options.staleTime); -} diff --git a/packages/fl_query/lib/src/core/retryer.dart b/packages/fl_query/lib/src/core/retryer.dart deleted file mode 100644 index e6d1b33..0000000 --- a/packages/fl_query/lib/src/core/retryer.dart +++ /dev/null @@ -1,202 +0,0 @@ -import 'dart:async'; - -import 'dart:math' show pow, min; - -import 'package:fl_query/src/core/online_manager.dart'; - -typedef ShouldRetryFunction = int Function( - int failureCount, - TError error, -); -typedef RetryDelayFunction = double Function( - int failureCount, - TError error, -); - -double defaultRetryDelay(int failureCount) { - return min(pow(1000 * 2, failureCount), 30000).toDouble(); -} - -abstract class Cancelable { - void cancel(); -} - -bool isCancelable(value) { - return value is Cancelable; -} - -class CancelledError { - bool? revert; - bool? silent; - CancelledError({this.revert, this.silent}); - - @override - String toString() { - return "CancelledError(revert: $revert, silent: $silent)"; - } -} - -bool isCancelledError(value) { - return value is CancelledError; -} - -typedef OnError = void Function(TError error); -typedef OnData> = void Function(TData data); - -class Retryer, TError> { - late void Function({bool? revert, bool? silent}) cancel; - late void Function() cancelRetry; - late void Function() continueRetry; - late void Function() continueFn; - // late Future future; - late Completer completer; - int failureCount; - bool isPaused; - bool isResolved; - bool isTransportCancelable; - - // config options for the retryer - FutureOr Function() fn; - void Function()? _abort; - OnError? onError; - OnData? onSuccess; - void Function(int failureCount, TError error)? onFail; - void Function()? onPause; - void Function()? onContinue; - ShouldRetryFunction? retry; - RetryDelayFunction? retryDelay; - - Retryer({ - required this.fn, - void Function()? abort, - this.onError, - this.onSuccess, - this.onFail, - this.onPause, - this.onContinue, - this.retry, - this.retryDelay, - }) : _abort = abort, - failureCount = 0, - isPaused = false, - isResolved = false, - isTransportCancelable = false { - bool cancelRetry = false; - void Function({bool? revert, bool? silent})? cancelFn; - void Function([dynamic value])? continueFn; - cancel = ({bool? revert, bool? silent}) { - cancelFn?.call(); - }; - - this.cancelRetry = () { - cancelRetry = true; - }; - - this.continueRetry = () { - cancelRetry = false; - }; - - this.continueFn = () => continueFn?.call(); - - completer = Completer(); - - // this.future = completer.future; - - resolve(value) { - if (!this.isResolved) { - this.isResolved = true; - onSuccess?.call(value); - continueFn?.call(); - if (!completer.isCompleted) completer.complete(value); - } - } - - reject(value) { - if (!this.isResolved) { - this.isResolved = true; - onError?.call(value); - continueFn?.call(); - if (!completer.isCompleted) completer.completeError(value); - } - } - - pause() { - Completer pauseCompleter = Completer(); - if (!pauseCompleter.isCompleted) continueFn = pauseCompleter.complete; - this.isPaused = true; - onPause?.call(); - return pauseCompleter.future.then((val) { - continueFn = null; - this.isPaused = false; - onContinue?.call(); - }); - } - - run() { - // Do nothing if already resolved - if (this.isResolved) { - return; - } - var promiseOrValue; - - // Execute query - try { - promiseOrValue = fn(); - } catch (error) { - promiseOrValue = Future.error(error); - } - - // Create callback to cancel this fetch - cancelFn = ({bool? revert, bool? silent}) { - if (!this.isResolved) { - reject(new CancelledError(revert: revert, silent: silent)); - - abort?.call(); - - // Cancel transport if supported - if (isCancelable(promiseOrValue)) { - try { - promiseOrValue.cancel(); - } catch (error) {} - } - } - }; - - // Check if the transport layer support cancellation - this.isTransportCancelable = isCancelable(promiseOrValue); - Future.value(promiseOrValue).then(resolve).catchError((error) { - // Stop if the fetch is already resolved - if (this.isResolved) return; - // Do we need to retry the request? - int _retry = retry?.call(failureCount, error) ?? 3; - double _retryDelay = retryDelay?.call(failureCount, error) ?? - defaultRetryDelay(failureCount); - bool shouldRetry = _retry > 0 && _retry > failureCount; - if (cancelRetry || !shouldRetry) { - // We are done if the query does not need to be retried - reject(error); - return; - } - this.failureCount++; - - // Notify on fail - onFail?.call(this.failureCount, error); - Future.delayed(Duration(milliseconds: _retryDelay.toInt())) - .then((val) async { - if (!await onlineManager.isOnline()) { - return pause(); - } - }).then((val) { - if (cancelRetry) { - reject(error); - } else { - run(); - } - }); - }); - } - - // Start loop - run(); - } -} diff --git a/packages/fl_query/lib/src/core/subscribable.dart b/packages/fl_query/lib/src/core/subscribable.dart deleted file mode 100644 index 118a6b1..0000000 --- a/packages/fl_query/lib/src/core/subscribable.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:meta/meta.dart'; - -//? using a single argument due to TypeCast Error cause queryObserver -//? listeners -void placeholder(a1) {} - -abstract class Subscribable { - @protected - List listeners; - Subscribable() : listeners = []; - - void Function() subscribe([TListener? listener]) { - listener ??= placeholder as TListener; - - listeners.add(listener); - - onSubscribe(); - - return () { - listeners = listeners.where((x) => x != listener).toList(); - onUnsubscribe(); - }; - } - - bool hasListeners() { - return listeners.isNotEmpty; - } - - @protected - void onSubscribe() {} - - @protected - void onUnsubscribe() {} -} diff --git a/packages/fl_query/lib/src/core/utils.dart b/packages/fl_query/lib/src/core/utils.dart deleted file mode 100644 index 071a550..0000000 --- a/packages/fl_query/lib/src/core/utils.dart +++ /dev/null @@ -1,175 +0,0 @@ -import 'package:fl_query/src/core/models.dart'; -import 'package:fl_query/src/core/query.dart'; -import 'package:fl_query/src/core/query_key.dart'; -import 'package:collection/collection.dart'; -import 'dart:math'; - -/// Default query keys hash function. -/// Dummy function just to fill the gaps for original react-query like -/// function body signatures -/// It is not required as a Standardized [QueryKey] data-class is used to -/// create the queryKey -String hashQueryKeyByOptions( - QueryKey queryKey, - QueryOptions? options, -) { - return options?.queryKeyHashFn?.call(queryKey) ?? queryKey.key; -} - -enum QueryStatusFilter { - all, - active, - inactive, - none, -} - -QueryStatusFilter mapQueryStatusFilter( - bool? active, - bool? inactive, -) { - if ((active == true && inactive == true) || - (active == null && inactive == null)) { - return QueryStatusFilter.all; - } else if (active == false && inactive == false) { - return QueryStatusFilter.none; - } else { - // At this point, active|inactive can only be true|false or false|true - // so, when only one value is provided, the missing one has to be the negated value - bool isActive = active ?? !(inactive ?? false); - return isActive ? QueryStatusFilter.active : QueryStatusFilter.inactive; - } -} - -bool matchQuery( - QueryFilters filters, - Query query, [ - - /// multiple queryKeys to find the query - QueryKey? queryKeys, -]) { - if (queryKeys != null) { - if (filters.exact == true && - query.queryHash != hashQueryKeyByOptions(queryKeys, query.options)) - return false; - else if (query.queryKey.key != queryKeys.key && - !queryKeys.keyAsList.contains(query.queryKey.key) && - !query.queryKey.keyAsList.contains(queryKeys.key)) return false; - } - QueryStatusFilter queryStatusFilter = - mapQueryStatusFilter(filters.active, filters.inactive); - - if (queryStatusFilter == QueryStatusFilter.none) { - return false; - } else if (queryStatusFilter != QueryStatusFilter.all) { - bool isActive = query.isActive(); - if (queryStatusFilter == QueryStatusFilter.active && !isActive) { - return false; - } - if (queryStatusFilter == QueryStatusFilter.inactive && isActive) { - return false; - } - } - - if (filters.stale != null && query.isStale() != filters.stale) { - return false; - } - - if (filters.fetching != null && query.isFetching() != filters.fetching) { - return false; - } - - if (filters.predicate != null && !filters.predicate!(query)) { - return false; - } - - return true; -} - -void noop([e]) => null; - -bool shallowEqualMap(Map? a, Map? b) { - if ((a != null && b == null) || (b != null && a == null)) { - return false; - } - - for (final item in a!.entries) { - if (a[item.key] != b?[item.key]) return false; - } - - return true; -} - -/// This function returns `a` if `b` is deeply equal\ -/// If not, it will replace any deeply equal children of `b` with those -/// of `a`\ -/// This can be used for structural sharing between JSON values for example. -/// `a` & `b` can only be Type of [List] or [Map] -replaceEqualDeep(a, b) { - if (a == b) { - return a; - } - - int aSize; - List bItems; - int bSize; - int equalItems = 0; - onEqual() => equalItems++; - var copy; - if (a is List && b is List) { - aSize = a.length; - bItems = b; - bSize = bItems.length; - copy = replaceEqualDeepList(a, b, onEqual); - } else if (a is Map && b is Map) { - aSize = a.keys.length; - bItems = b.keys.toList(); - bSize = bItems.length; - copy = replaceEqualDeepMap(a, b, onEqual); - } else { - return b; - } - return aSize == bSize && equalItems == aSize ? a : copy; -} - -Map replaceEqualDeepMap(Map a, Map b, void Function() onEqual) { - final copy = Map.from(a); - copy.clear(); - for (final bEntry in b.entries) { - final aItem = a[bEntry.key]; - copy[bEntry.key] = - aItem != null ? replaceEqualDeep(aItem, bEntry.value) : bEntry.value; - if (copy[bEntry.key] == aItem) { - onEqual(); - } - } - return copy; -} - -List replaceEqualDeepList(List a, List b, void Function() onEqual) { - final copy = List.of(a, growable: true); - copy.clear(); - for (final bEntry in b.asMap().entries) { - final aItem = a.firstWhereIndexedOrNull((i, _) => i == bEntry.key); - final result = - aItem != null ? replaceEqualDeep(aItem, bEntry.value) : bEntry.value; - copy.add(result); - if (copy.last == aItem) { - onEqual(); - } - } - return copy; -} - -Duration timeUntilStale(DateTime updatedAt, [Duration? staleTime]) { - return Duration( - milliseconds: max( - updatedAt - .add(staleTime ?? Duration.zero) - .difference(DateTime.now()) - .inMilliseconds, - 0, - ), - ); -} - -typedef DataUpdateFunction = TOutput Function(TInput input); diff --git a/packages/fl_query/lib/utils.dart b/packages/fl_query/lib/utils.dart new file mode 100644 index 0000000..b126c3f --- /dev/null +++ b/packages/fl_query/lib/utils.dart @@ -0,0 +1,10 @@ +import 'package:fl_query/query.dart'; + +Future callQueryListeners(Set> listeners, T data) { + return Future.wait(listeners.map( + (listener) => Future.value(listener(data)), + )); + // for (final listener in listeners) { + // await listener(data); + // } +} diff --git a/packages/fl_query/pubspec.yaml b/packages/fl_query/pubspec.yaml index 842a822..929e1c5 100644 --- a/packages/fl_query/pubspec.yaml +++ b/packages/fl_query/pubspec.yaml @@ -4,28 +4,52 @@ version: 0.0.1 homepage: https://github.com/KRTirtho/fl-query environment: - sdk: ">=2.15.1 <3.0.0" - # flutter: ">=1.17.0" + sdk: ">=2.17.1 <3.0.0" + flutter: ">=1.17.0" dependencies: - # flutter: - # sdk: flutter - meta: ^1.3.0 - path: ^1.8.0 - hive: ^2.0.0 - http: ^0.13.0 - collection: ^1.15.0 - uuid: ^3.0.1 - internet_connection_checker: ^0.0.1+3 + collection: ^1.16.0 + flutter: + sdk: flutter dev_dependencies: - async: ^2.5.0 - mockito: ^5.0.0 - test: ^1.18.2 - coverage: ^1.0.3 - http_parser: ^4.0.0 - lints: ^1.0.1 - build_runner: ^2.1.7 + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 -# The following section is specific to Flutter. -# flutter: +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # To add assets to your package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # To add custom fonts to your package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/packages/fl_query/test/helpers/utils.dart b/packages/fl_query/test/helpers/utils.dart deleted file mode 100644 index 9a73ba2..0000000 --- a/packages/fl_query/test/helpers/utils.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'package:fl_query/src/core/core.dart'; -import 'package:uuid/uuid.dart'; - -Uuid uuid = Uuid(); - -QueryKey queryKey() { - return QueryKey("query_${uuid.v4()}"); -} - -class SpyFn { - int _calls = 0; - late T _customFn; - int get calls => _calls; - T get customFn => _customFn; - - SpyFn(); - - SpyFn.withFn(this._customFn); - - fn([Function()? cb]) { - _calls++; - return () => cb?.call(); - } - - fn1([Function()? cb]) { - _calls++; - return (p0) => cb?.call(); - } - - fn2([Function()? cb]) { - _calls++; - return (p0, p1) => cb?.call(); - } - - fn3([Function()? cb]) { - _calls++; - return (p0, p1, p2) => cb?.call(); - } - - fn4([Function()? cb]) { - _calls++; - return (p0, p1, p2, p3) => cb?.call(); - } - - fn5([Function()? cb]) { - _calls++; - return (p0, p1, p2, p3, p4) => cb?.call(); - } -} - -Future sleep(int ms) => Future.delayed(Duration(milliseconds: ms)); diff --git a/packages/fl_query/test/src/core/notify_manager_test.dart b/packages/fl_query/test/src/core/notify_manager_test.dart deleted file mode 100644 index c2e22c5..0000000 --- a/packages/fl_query/test/src/core/notify_manager_test.dart +++ /dev/null @@ -1,68 +0,0 @@ -import 'package:fl_query/src/core/notify_manager.dart'; -import 'package:test/expect.dart'; -import 'package:test/scaffolding.dart'; - -class SpyNotifyManager extends NotifyManager { - SpyNotifyManager() : super(); - - int flushCall = 0; - - @override - void flush() { - super.flush(); - flushCall++; - } -} - -void main() { - group("NotifyManager", () { - test( - "Should call _notifyFn in schedule When no callback is batched", - () async { - final NotifyManager notifyManager = NotifyManager(); - int called = 0; - notifyManager.schedule(() => called++); - await Future.delayed(Duration(milliseconds: 1)); - expect(called, equals(1)); - }, - ); - - test( - "Should call default _batchNotifyFn even When multiple level deep callbacks are registered", - () async { - final NotifyManager notifyManager = NotifyManager(); - int level1 = 0; - int level2 = 0; - int level3 = 0; - callback() async { - await Future.delayed(Duration(milliseconds: 20)); - level3++; - } - - notifyManager.batch(() { - notifyManager.batch(() { - notifyManager.schedule(callback); - level2++; - }); - level1++; - }); - await Future.delayed(Duration(milliseconds: 30)); - expect(level1, equals(1)); - expect(level2, equals(1)); - expect(level3, equals(1)); - }, - timeout: Timeout(Duration(minutes: 2)), - ); - - test("Should flush When Exception is thrown in a batched callback", () { - final SpyNotifyManager notifyManager = SpyNotifyManager(); - try { - notifyManager.batch(() { - throw Exception("Damn an exception"); - }); - } catch (e) {} - - expect(notifyManager.flushCall, equals(1)); - }); - }); -} diff --git a/packages/fl_query/test/src/core/online_manager_test.dart b/packages/fl_query/test/src/core/online_manager_test.dart deleted file mode 100644 index 0c6db8b..0000000 --- a/packages/fl_query/test/src/core/online_manager_test.dart +++ /dev/null @@ -1,116 +0,0 @@ -import 'dart:async'; - -import 'package:fl_query/src/core/online_manager.dart'; -import 'package:internet_connection_checker/internet_connection_checker.dart'; -import 'package:mockito/annotations.dart'; -import 'package:mockito/mockito.dart'; -import 'package:test/expect.dart'; -import 'package:test/scaffolding.dart'; -import './online_manager_test.mocks.dart'; - -@GenerateMocks([InternetConnectionChecker]) -void main() { - group('OnlineManager', () { - late OnlineManager onlineManagerTest; - late StreamController statusController; - late MockInternetConnectionChecker connectionChecker; - setUp(() { - statusController = StreamController.broadcast(); - statusController.add(InternetConnectionStatus.connected); - connectionChecker = MockInternetConnectionChecker(); - when(connectionChecker.hasConnection) - .thenAnswer((_) => Future.value(true)); - when(connectionChecker.hasListeners) - .thenReturn(statusController.hasListener); - when(connectionChecker.onStatusChange) - .thenAnswer((_) => statusController.stream); - onlineManagerTest = OnlineManager(connectionChecker); - }); - - tearDown(() { - statusController.close(); - }); - - test( - 'isOnline Should return true When InternetConnectionChecker.hasConnection is true', - () async { - bool online = await onlineManagerTest.isOnline(); - expect(online, isTrue); - }, - ); - - test( - "setEventListener Should use _online property When setOnline sets _online = false", - () async { - int count = 0; - - setup(void Function(bool?) setOnline) { - Timer(Duration(milliseconds: 20), () { - count++; - setOnline(false); - }); - return () {}; - } - - onlineManagerTest.setEventListener(setup); - await Future.delayed(Duration(milliseconds: 30)); - expect(count, equals(1)); - onlineManagerTest.isOnline().then((online) { - expect(online, isFalse); - }); - }, - ); - - test( - 'setEventListener Should call previous remove handler When replacing an event listener', - () { - int cb1calls = 0; - int cb2calls = 0; - onlineManagerTest.setEventListener((_) => () => cb1calls++); - onlineManagerTest.setEventListener((_) => () => cb2calls++); - expect(cb1calls, equals(1)); - expect(cb2calls, equals(0)); - }, - ); - test( - 'Should replace default window listener When a new event listener is set', - () { - // Should set the default event listener with window event listeners - final unsubscribe = onlineManagerTest.subscribe(); - verify(connectionChecker.onStatusChange.listen).called(1); - // Should replace the window default event listener by a new one - // and it should call window.removeEventListener twice - onlineManagerTest.setEventListener((online) { - return () => null; - }); - expect(connectionChecker.hasListeners, isFalse); - unsubscribe(); - }, - ); - - test('Should cancel StreamSubscription When last listener unsubscribes', - () { - final unsubscribe1 = onlineManager.subscribe(() => null); - final unsubscribe2 = onlineManager.subscribe(() => null); - - verify(connectionChecker.onStatusChange.listen).called(1); - unsubscribe1(); - expect(connectionChecker.hasListeners, isTrue); - unsubscribe2(); - expect(connectionChecker.hasListeners, isFalse); - }, skip: true); - - test('should keep setup function even if last listener unsubscribes', () { - int count = 0; - onlineManager.setEventListener((_) => () => count++); - - final unsubscribe1 = onlineManagerTest.subscribe(() => null); - expect(count, equals(1)); - unsubscribe1(); - - final unsubscribe2 = onlineManager.subscribe(() => null); - expect(count, equals(2)); - unsubscribe2(); - }, skip: true); - }); -} diff --git a/packages/fl_query/test/src/core/online_manager_test.mocks.dart b/packages/fl_query/test/src/core/online_manager_test.mocks.dart deleted file mode 100644 index fc3a6c1..0000000 --- a/packages/fl_query/test/src/core/online_manager_test.mocks.dart +++ /dev/null @@ -1,82 +0,0 @@ -// Mocks generated by Mockito 5.1.0 from annotations -// in fl_query/test/src/core/online_manager_test.dart. -// Do not manually edit this file. - -import 'dart:async' as _i3; - -import 'package:internet_connection_checker/internet_connection_checker.dart' - as _i2; -import 'package:mockito/mockito.dart' as _i1; - -// ignore_for_file: type=lint -// ignore_for_file: avoid_redundant_argument_values -// ignore_for_file: avoid_setters_without_getters -// ignore_for_file: comment_references -// ignore_for_file: implementation_imports -// ignore_for_file: invalid_use_of_visible_for_testing_member -// ignore_for_file: prefer_const_constructors -// ignore_for_file: unnecessary_parenthesis -// ignore_for_file: camel_case_types - -class _FakeDuration_0 extends _i1.Fake implements Duration {} - -class _FakeAddressCheckResult_1 extends _i1.Fake - implements _i2.AddressCheckResult {} - -/// A class which mocks [InternetConnectionChecker]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockInternetConnectionChecker extends _i1.Mock - implements _i2.InternetConnectionChecker { - MockInternetConnectionChecker() { - _i1.throwOnMissingStub(this); - } - - @override - List<_i2.AddressCheckOptions> get addresses => - (super.noSuchMethod(Invocation.getter(#addresses), - returnValue: <_i2.AddressCheckOptions>[]) - as List<_i2.AddressCheckOptions>); - @override - set addresses(List<_i2.AddressCheckOptions>? _addresses) => - super.noSuchMethod(Invocation.setter(#addresses, _addresses), - returnValueForMissingStub: null); - @override - Duration get checkInterval => - (super.noSuchMethod(Invocation.getter(#checkInterval), - returnValue: _FakeDuration_0()) as Duration); - @override - set checkInterval(Duration? _checkInterval) => - super.noSuchMethod(Invocation.setter(#checkInterval, _checkInterval), - returnValueForMissingStub: null); - @override - _i3.Future get hasConnection => - (super.noSuchMethod(Invocation.getter(#hasConnection), - returnValue: Future.value(false)) as _i3.Future); - @override - _i3.Future<_i2.InternetConnectionStatus> get connectionStatus => - (super.noSuchMethod(Invocation.getter(#connectionStatus), - returnValue: Future<_i2.InternetConnectionStatus>.value( - _i2.InternetConnectionStatus.connected)) - as _i3.Future<_i2.InternetConnectionStatus>); - @override - _i3.Stream<_i2.InternetConnectionStatus> get onStatusChange => - (super.noSuchMethod(Invocation.getter(#onStatusChange), - returnValue: Stream<_i2.InternetConnectionStatus>.empty()) - as _i3.Stream<_i2.InternetConnectionStatus>); - @override - bool get hasListeners => - (super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) - as bool); - @override - bool get isActivelyChecking => - (super.noSuchMethod(Invocation.getter(#isActivelyChecking), - returnValue: false) as bool); - @override - _i3.Future<_i2.AddressCheckResult> isHostReachable( - _i2.AddressCheckOptions? options) => - (super.noSuchMethod(Invocation.method(#isHostReachable, [options]), - returnValue: Future<_i2.AddressCheckResult>.value( - _FakeAddressCheckResult_1())) - as _i3.Future<_i2.AddressCheckResult>); -} diff --git a/packages/fl_query/test/src/core/query_cache_test.dart b/packages/fl_query/test/src/core/query_cache_test.dart deleted file mode 100644 index 88de98d..0000000 --- a/packages/fl_query/test/src/core/query_cache_test.dart +++ /dev/null @@ -1,300 +0,0 @@ -import 'package:fl_query/src/core/core.dart'; -import 'package:fl_query/src/core/query_cache.dart'; -import 'package:fl_query/src/core/retryer.dart'; -import 'package:test/expect.dart'; -import 'package:test/scaffolding.dart'; - -import '../../helpers/utils.dart'; - -void main() { - group('QueryCache', () { - late QueryClient queryClient; - late QueryCache queryCache; - - setUp(() { - queryClient = new QueryClient(); - queryCache = queryClient.getQueryCache(); - }); - - tearDown(() { - queryClient.clear(); - }); - group('subscribe', () { - test('Should pass the correct query', () async { - final QueryKey key = queryKey(); - var _event; - subscriber(event) { - _event ??= event; - } - - final unsubscribe = queryCache.subscribe(subscriber); - final Map data = {'foo': "foo"}; - queryClient.setQueryData(key, (_) => data); - final query = queryCache.find(key); - await Future.delayed(Duration(milliseconds: 1)); - expect(_event, isA()); - expect( - (_event as QueryCacheNotifyEvent).type, - QueryCacheNotifyEventType.queryAdded, - ); - expect( - (_event as QueryCacheNotifyEvent).query, - same(query), - ); - unsubscribe(); - }); - - test('Should notify listeners When new query is added', () async { - final key = queryKey(); - late bool called; - callback(_) { - called = true; - } - - queryCache.subscribe(callback); - queryClient.prefetchQuery( - queryKey: key, - queryFn: (_) async => {'data': "data"}, - ); - await Future.delayed(Duration(milliseconds: 100)); - - expect(called, isTrue); - }); - - test('Should include the queryCache and query When notifying listeners', - () async { - final key = queryKey(); - QueryCacheNotifyEvent? _event; - callback(event) => _event ??= event; - queryCache.subscribe(callback); - queryClient.prefetchQuery( - queryKey: key, - queryFn: (_) => {'data': "data"}, - ); - final query = queryCache.find(key); - await Future.delayed(Duration(milliseconds: 100)); - expect(_event, isA()); - expect(_event?.type, QueryCacheNotifyEventType.queryAdded); - expect(_event?.query, same(query)); - }); - - test('Should notify subscribers When new query with initialData is added', - () async { - final key = queryKey(); - late bool called; - callback(_) => called = true; - queryCache.subscribe(callback); - queryClient - .prefetchQuery, dynamic, Map>( - queryKey: key, - queryFn: (_) => {'data': "Data"}, - options: FetchQueryOptions(initialData: {"data": "initial-data"}), - ); - await Future.delayed(Duration(milliseconds: 100)); - expect(called, isTrue); - }); - }); - - group('find', () { - test('Should filter correctly', () async { - final key = queryKey(); - await queryClient.prefetchQuery( - queryKey: key, - queryFn: (_) => {"data": 'data1'}, - ); - final query = queryCache.find(key); - expect(query, isNotNull); - }); - - test( - 'Should filter correctly When called with exact set to false', - () async { - final key = queryKey(); - await queryClient.prefetchQuery( - queryKey: key, - queryFn: (_) => {"data": 'data1'}, - ); - final query = queryCache.find(key, QueryFilters(exact: false)); - expect(query, isNotNull); - }, - ); - }); - - group('findAll', () { - test('Should filter correctly', () async { - final key1 = queryKey(); - final key2 = queryKey(); - final key3 = QueryKey.fromList(['posts', "1"]); - await queryClient.prefetchQuery( - queryKey: key1, - queryFn: (_) => {"data": 'data1'}, - ); - await queryClient.prefetchQuery( - queryKey: key2, - queryFn: (_) => {"data": 'data2'}, - ); - await queryClient.prefetchQuery( - queryKey: key3, - queryFn: (_) => {"data": 'data4'}, - ); - await queryClient.invalidateQueries(queryKeys: key2); - final query1 = queryCache.find(key1); - final query2 = queryCache.find(key2); - final query4 = queryCache.find(key3); - - expect(queryCache.findAll(key1), equals([query1])); - expect(queryCache.findAll(), equals([query1, query2, query4])); - expect( - queryCache.findAll(key1, QueryFilters(active: false)), - equals([query1]), - ); - expect( - queryCache.findAll(key1, QueryFilters(active: true)), equals([])); - expect(queryCache.findAll(key1, QueryFilters(stale: true)), equals([])); - expect( - queryCache.findAll(key1, QueryFilters(stale: false)), - equals([query1]), - ); - expect( - queryCache.findAll(key1, QueryFilters(stale: false, active: true)), - equals([]), - ); - expect( - queryCache.findAll(key1, QueryFilters(active: false, stale: false)), - equals([query1]), - ); - expect( - queryCache.findAll( - key1, - QueryFilters(active: false, stale: false, exact: true), - ), - equals([query1]), - ); - - expect(queryCache.findAll(key2), equals([query2])); - expect( - queryCache.findAll(key2, QueryFilters(stale: null)), - equals([query2]), - ); - expect( - queryCache.findAll(key2, QueryFilters(stale: true)), - equals([query2]), - ); - expect( - queryCache.findAll(key2, QueryFilters(stale: false)), - equals([]), - ); - - expect( - queryCache.findAll( - null, - QueryFilters(predicate: (query) => query == query4), - ), - equals([query4]), - ); - expect(queryCache.findAll(QueryKey('posts')), equals([query4])); - }); - - test('Should return all the queries When no filters are defined', - () async { - final key1 = queryKey(); - final key2 = queryKey(); - await queryClient.prefetchQuery( - queryKey: key1, - queryFn: (_) => {"data": 'data1'}, - ); - await queryClient.prefetchQuery( - queryKey: key2, - queryFn: (_) { - return {"data": 'data2'}; - }, - ); - expect(queryCache.findAll().length, 2); - }); - }); - - group('QueryCacheConfig.onError', () { - test('should be called when a query errors', () async { - final key = queryKey(); - var errorArg; - var queryArg; - onError(error, query) { - errorArg = error; - queryArg = query; - } - - final testCache = new QueryCache(onError: onError); - final testClient = new QueryClient(queryCache: testCache); - await testClient - .prefetchQuery, dynamic, Map>( - queryKey: key, queryFn: (_) => Future.error('error')); - final query = testCache.find(key); - expect(errorArg, equals("error")); - expect(queryArg, equals(query)); - }); - }); - - group('QueryCacheConfig.onSuccess', () { - test('should be called when a query is successful', () async { - final key = queryKey(); - var dataArg; - var queryArg; - onData(data, query) { - dataArg = data; - queryArg = query; - } - - final testCache = new QueryCache(onData: onData); - final testClient = new QueryClient(queryCache: testCache); - await testClient - .prefetchQuery, dynamic, Map>( - queryKey: key, - queryFn: (_) => Future.value({"data": 5}), - ); - final query = testCache.find(key); - expect(dataArg, equals({"data": 5})); - expect(queryArg, equals(query)); - }); - }); - group('QueryCache.add', () { - test('should not try to add a query already added to the cache', - () async { - final key = queryKey(); - final hash = key.key; - - await queryClient.prefetchQuery( - queryKey: key, queryFn: (_) => {"data": 'data1'}); - - // Directly add the query from the cache - // to simulate a race condition - final query = queryCache.queriesMap[hash] as Query; - - // No error should be thrown when trying to add the query - queryCache.add(query); - expect(queryCache.queries.length, 1); - - // Clean-up to avoid an error when queryClient.clear() - queryCache.remove(query); - }); - }); - - group('QueryCache.remove', () { - test('should not try to remove a query already removed from the cache', - () async { - final key = queryKey(); - final hash = key.key; - - await queryClient.prefetchQuery( - queryKey: key, queryFn: (_) => {"data": 'data1'}); - - // Directly remove the query from the cache - // to simulate a race condition - final query = queryCache.queriesMap[hash] as Query; - queryCache.queriesMap.remove(hash); - - // No error should be thrown when trying to remove the query - expect(() => queryCache.remove(query), isNot(throwsException)); - }); - }); - }); -} diff --git a/packages/fl_query/test/src/core/query_observer_test.dart b/packages/fl_query/test/src/core/query_observer_test.dart deleted file mode 100644 index df1d921..0000000 --- a/packages/fl_query/test/src/core/query_observer_test.dart +++ /dev/null @@ -1,912 +0,0 @@ -import 'dart:async'; - -import 'package:fl_query/src/core/core.dart'; -import 'package:fl_query/src/core/query_observer.dart'; -import 'package:test/test.dart'; - -import '../../helpers/utils.dart'; - -typedef QueryFn = FutureOr> Function( - QueryFunctionContext); - -void main() { - group('QueryObserver', () { - late QueryClient queryClient; - - setUp(() { - queryClient = QueryClient(); - queryClient.mount(); - }); - - tearDown(() { - queryClient.clear(); - }); - - test('should trigger a fetch when subscribed', () async { - final key = queryKey(); - ; - int calls = 0; - queryFn(context) { - calls++; - return {"data": "data1"}; - } - - final observer = QueryObserver( - queryClient, - QueryObserverOptions(queryKey: key, queryFn: queryFn), - ); - final unsubscribe = observer.subscribe(); - await Future.delayed(Duration(milliseconds: 1)); - unsubscribe(); - expect(calls, 1); - }); - - test('should notify when switching query', () async { - final key1 = queryKey(); - final key2 = queryKey(); - final List results = []; - final observer = QueryObserver( - queryClient, - QueryObserverOptions( - queryKey: key1, - queryFn: (_) => {"data": 1}, - ), - ); - final unsubscribe = observer.subscribe((result) { - results.add(result); - }); - await Future.delayed(Duration(milliseconds: 1)); - observer.setOptions( - QueryObserverOptions(queryKey: key2, queryFn: (_) => {"data": 2}), - ); - await Future.delayed(Duration(milliseconds: 2)); - unsubscribe(); - expect(results.length, 4); - expect(results[0].data, isNull); - expect(results[0].status, QueryStatus.loading); - expect(results[1].data, {"data": 1}); - expect(results[1].status, QueryStatus.success); - expect(results[2].data, isNull); - expect(results[2].status, QueryStatus.loading); - expect(results[3].data, {"data": 2}); - expect(results[3].status, QueryStatus.success); - }); - - test('should be able to fetch with a selector', () async { - final key = queryKey(); - ; - final observer = QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: (_) => {"count": 1}, - select: (data) => ({"myCount": data?["count"]}), - )); - QueryObserverResult? observerResult; - final unsubscribe = observer.subscribe((result) { - observerResult = result; - }); - await Future.delayed(Duration(milliseconds: 1)); - unsubscribe(); - expect( - observerResult?.data, - equals({"myCount": 1}), - ); - }); - - test('should be able to fetch with a selector using the fetch method', - () async { - final key = queryKey(); - ; - final observer = QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: (_) => {"count": 1}, - select: (data) => ({"myCount": data?["count"]}), - )); - final observerResult = await observer.refetch(); - expect(observerResult?.data, equals({"myCount": 1})); - }); - - test('should run the selector again if the data changed', () async { - final key = queryKey(); - ; - int count = 0; - final observer = QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: (_) => Map.from({"count": count}), - select: (data) { - count++; - return Map.from({"myCount": data?["count"]}); - }, - )); - final observerResult1 = await observer.refetch(); - final observerResult2 = await observer.refetch(); - expect(count, 2); - expect(observerResult1?.data, equals({"myCount": 0})); - expect(observerResult2?.data, equals({"myCount": 1})); - }); - - test('should run the selector again if the selector changed', () async { - final key = queryKey(); - ; - int count = 0; - final List results = []; - final queryFn = (_) => ({"count": 1}); - select1(data) { - count++; - return {"myCount": data?["count"]}; - } - - select2(_data) { - count++; - return {"myCount": 99}; - } - - final observer = new QueryObserver( - queryClient, - QueryObserverOptions( - queryKey: key, - queryFn: queryFn, - select: select1, - )); - final unsubscribe = observer.subscribe((result) { - results.add(result); - }); - await Future.delayed(Duration(milliseconds: 1)); - observer.setOptions(QueryObserverOptions( - queryKey: key, - queryFn: queryFn, - select: select2, - )); - await Future.delayed(Duration(milliseconds: 1)); - //! Currently causing an extra call for refetch - //! select shouldn't be called when refetch is called - await observer.refetch(); - unsubscribe(); - expect(count, 2); - expect(results.length, 5); - expect(results.first.status, QueryStatus.loading); - expect(results.first.isFetching, isTrue); - expect(results.first.data, isNull); - expect(results[1].status, QueryStatus.success); - expect(results[1].isFetching, false); - expect(results[1].data, {"myCount": 1}); - expect(results[2].status, QueryStatus.success); - expect(results[2].isFetching, false); - expect(results[2].data, {"myCount": 99}); - expect(results[3].status, QueryStatus.success); - expect(results[3].isFetching, true); - expect(results[3].data, {"myCount": 99}); - expect(results.last.status, QueryStatus.success); - expect(results.last.isFetching, false); - expect(results.last.data, {"myCount": 99}); - }); - test( - 'should not run the selector again if the data and selector did not change', - () async { - final key = queryKey(); - ; - int count = 0; - final List results = []; - final queryFn = (_) => {"count": 1}; - select(data) { - count++; - return {"myCount": data["count"]}; - } - - final observer = new QueryObserver( - queryClient, - QueryObserverOptions( - queryKey: key, - queryFn: queryFn, - select: select, - )); - final unsubscribe = observer.subscribe((result) { - results.add(result); - }); - await Future.delayed(Duration(milliseconds: 1)); - observer.setOptions(QueryObserverOptions( - queryKey: key, - queryFn: queryFn, - select: select, - )); - await Future.delayed(Duration(milliseconds: 1)); - await observer.refetch(); - unsubscribe(); - expect(count, 1); - expect(results.length, 4); - expect(results.first.status, QueryStatus.loading); - expect(results.first.isFetching, isTrue); - expect(results.first.data, isNull); - expect(results[1].status, QueryStatus.success); - expect(results[1].isFetching, false); - expect(results[1].data, {"myCount": 1}); - expect(results[2].status, QueryStatus.success); - expect(results[2].isFetching, true); - expect(results[2].data, {"myCount": 1}); - expect(results.last.status, QueryStatus.success); - expect(results.last.isFetching, false); - expect(results.last.data, {"myCount": 1}); - }); - - test('should not run the selector again if the data did not change', - () async { - final key = queryKey(); - ; - int count = 0; - final observer = new QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: (_) => {"count": 1}, - select: (data) { - count++; - return {"myCount": data?["count"]}; - }, - ), - ); - final observerResult1 = await observer.refetch(); - final observerResult2 = await observer.refetch(); - expect(count, 1); - expect(observerResult1?.data, equals({"myCount": 1})); - expect(observerResult2?.data, equals({"myCount": 1})); - }); - - test('should always run the selector again if selector throws an error', - () async { - final key = queryKey(); - ; - final List results = []; - select(data) { - throw new Exception('selector error'); - } - - queryFn(_) => ({"count": 1}); - final observer = new QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: queryFn, - select: select, - )); - final unsubscribe = observer.subscribe((result) { - results.add(result); - }); - await Future.delayed(Duration(milliseconds: 1)); - await observer.refetch(); - unsubscribe(); - expect(results.length, 5); - expect(results.first.status, QueryStatus.loading); - expect(results.first.isFetching, isTrue); - expect(results.first.data, isNull); - expect(results[1].status, QueryStatus.error); - expect(results[1].isFetching, false); - expect(results[1].data, isNull); - expect(results[2].status, QueryStatus.error); - expect(results[2].isFetching, true); - expect(results[2].data, isNull); - expect(results[3].status, QueryStatus.error); - expect(results[3].isFetching, false); - expect(results[3].data, isNull); - expect(results.last.status, QueryStatus.error); - expect(results.last.isFetching, false); - expect(results.last.data, isNull); - }); - - test('should structurally share the selector', () async { - final key = queryKey(); - ; - int count = 0; - final observer = new QueryObserver( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: (_) => {"count": ++count}, - select: (data) { - return {"myCount": 1}; - }, - )); - final observerResult1 = await observer.refetch(); - final observerResult2 = await observer.refetch(); - expect(count, 2); - expect(observerResult1?.data, isNotNull); - expect(observerResult1?.data, equals(observerResult2?.data)); - }); - - test('should not trigger a fetch when subscribed and disabled', () async { - int count = 0; - final key = queryKey(); - ; - final observer = new QueryObserver( - queryClient, - QueryObserverOptions( - queryKey: key, - queryFn: (_) { - count++; - return {"data": null}; - }, - enabled: false, - )); - final unsubscribe = observer.subscribe(); - await Future.delayed(Duration(milliseconds: 1)); - unsubscribe(); - expect(count, 0); - }); - - test('should not trigger a fetch when not subscribed', () async { - int count = 0; - final key = queryKey(); - ; - new QueryObserver( - queryClient, - QueryObserverOptions( - queryKey: key, - queryFn: (_) { - count++; - return {"data": null}; - }, - )); - await Future.delayed(Duration(milliseconds: 1)); - expect(count, 0); - }); - - test('should be able to watch a query without defining a query function', - () async { - int count = 0; - int subscribeCount = 0; - final key = queryKey(); - ; - queryFn(_) { - count++; - return {"data": null}; - } - - final observer = QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: queryFn, - enabled: false, - )); - - final unsubscribe = observer.subscribe((_) { - subscribeCount++; - }); - await queryClient.fetchQuery(queryKey: key, queryFn: queryFn); - unsubscribe(); - expect(count, 1); - expect(subscribeCount, 2); - }); - - test('should accept unresolved query config in update function', () async { - final key = queryKey(); - ; - final observer = QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - enabled: false, - )); - - final List results = []; - final unsubscribe = observer.subscribe((x) { - results.add(x); - }); - observer.setOptions( - QueryObserverOptions( - enabled: false, - staleTime: Duration(milliseconds: 10), - ), - ); - int count = 0; - - queryFn(_) { - count++; - return {"data": null}; - } - - await queryClient.fetchQuery(queryKey: key, queryFn: queryFn); - await sleep(100); - unsubscribe(); - expect(count, 1); - expect(results.length, 3); - expect(results[0].isStale, true); - expect(results[1].isStale, false); - expect(results[2].isStale, true); - }); - - test('should be able to handle multiple subscribers', () async { - final key = queryKey(); - - int count = 0; - - final observer = new QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - enabled: false, - )); - final List results1 = []; - final List results2 = []; - final unsubscribe1 = observer.subscribe((x) { - results1.add(x); - }); - final unsubscribe2 = observer.subscribe((x) { - results2.add(x); - }); - await queryClient - .fetchQuery, dynamic, Map>( - queryKey: key, - queryFn: (_) { - count++; - return {"data": false}; - }, - ); - await sleep(50); - unsubscribe1(); - unsubscribe2(); - expect(count, 1); - expect(results1.length, 2); - expect(results2.length, 2); - expect(results1[0].data?["data"], isNull); - expect(results1[1].data?["data"], isFalse); - expect(results2[0].data?["data"], isNull); - expect(results2[1].data?["data"], isFalse); - }); - - test('should be able to resolve a promise', () async { - final key = queryKey(); - int count = 0; - final observer = new QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - enabled: false, - )); - QueryObserverResult, dynamic>? value; - observer.getNextResult().then((x) { - value = x; - }); - queryClient - .prefetchQuery, dynamic, Map>( - queryKey: key, - queryFn: (_) { - count++; - return {"data": "a data"}; - }); - await sleep(50); - expect(count, 1); - expect(value?.data?["data"], "a data"); - }); - - test('should be able to resolve a promise with an error', () async { - final key = queryKey(); - final observer = new QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - enabled: false, - )); - var error; - await observer.getNextResult(true).catchError((e) async { - error = e; - return e; - }); - await queryClient - .prefetchQuery, dynamic, Map>( - queryKey: key, queryFn: (_) => Future.error('reject')); - await sleep(50); - expect(error, 'reject'); - }, skip: true); - - test('should stop retry when unsubscribing', () async { - int count = 0; - final key = queryKey(); - final observer = new QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: (_) { - count++; - return Future.error({"data": 'reject'}); - }, - retry: (_, __) => 10, - retryDelay: (_, __) => 50, - ), - ); - final unsubscribe = observer.subscribe(); - await sleep(70); - unsubscribe(); - await sleep(200); - expect(count, 2); - }, skip: true); - - test('should clear interval when unsubscribing to a refetchInterval query', - () async { - final key = queryKey(); - - final fetchData = - (_) => Future>.error({'data': null}); - final observer = new QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: fetchData, - cacheTime: Duration.zero, - refetchInterval: (_, __) => Duration(milliseconds: 1), - )); - final unsubscribe = observer.subscribe(); - // @ts-expect-error - expect(observer.refetchInterval, isNotNull); - unsubscribe(); - // @ts-expect-error - expect(observer.refetchInterval, isNull); - await sleep(10); - expect(queryClient.getQueryCache().find(key), isNull); - }); - - test( - 'uses placeholderData as non-cache data when loading a query with no data', - () async { - final key = queryKey(); - final observer = new QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: (_) => {'data': 'data'}, - placeholderData: {"data": 'placeholder'}, - )); - final result = observer.getCurrentResult(); - expect(result?.status, QueryStatus.success); - - final List results = []; - - final unsubscribe = observer.subscribe((x) { - results.add(x); - }); - - await sleep(10); - unsubscribe(); - - expect(results.length, 2); - expect(results.first.status, QueryStatus.success); - expect(results.first.data, equals({"data": 'placeholder'})); - expect(results.last.status, QueryStatus.success); - expect(results.last.data, equals({'data': 'data'})); - }); - - test( - 'the retryer should not throw an error when reject if the retrier is already resolved', - () async { - final key = queryKey(); - int count = 0; - - final observer = new QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: (_) { - count++; - return Future.error({'reject': count}); - }, - retry: (_, __) => 1, - retryDelay: (_, __) => 20, - )); - - final unsubscribe = observer.subscribe(); - - // Simulate a race condition when an unsubscribe and a retry occur. - await sleep(20); - unsubscribe(); - - // A second reject is triggered for the retry - // but the retryer has already set isResolved to true - // so it does nothing and no error is thrown - - // Should not log an error - queryClient.clear(); - await sleep(40); - expect(true, true); - // expect(consoleMock).not.toHaveBeenNthCalledWith(1, 'reject 1') - - // consoleMock.mockRestore() - }, skip: true); - - test('getCurrentQuery should return the current query', () async { - final key = queryKey(); - - final observer = new QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, queryFn: (_) => {'data': 'data'})); - - expect(observer.getCurrentQuery().queryKey, key); - }); - - test('should throw an error if throwOnError option is true', () async { - final key = queryKey(); - - final observer = new QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: (_) => Future.error({'error': 'error'}), - retry: (_, __) => 0, - )); - - var error = null; - try { - await observer.refetch(options: RefetchOptions(throwOnError: true)); - } catch (err) { - error = err; - } - - expect(error, equals({'error': 'error'})); - }); - - test( - 'should not refetch in background if refetchIntervalInBackground is false', - () async { - final key = queryKey(); - final spy = SpyFn(); - - // focusManager.setFocused(false) - final observer = new QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: spy.fn1(), - refetchIntervalInBackground: false, - refetchInterval: (_, __) => Duration(milliseconds: 10), - )); - - final unsubscribe = observer.subscribe(); - await sleep(30); - - expect(spy.calls, 1); - - // Clean-up - unsubscribe(); - // focusManager.setFocused(true) - }); - - test( - 'should not use replaceEqualDeep for select value when structuralSharing option is true', - () async { - final key = queryKey(); - - final data = {"value": 'data'}; - final selectedData = {"value": 'data'}; - - final observer = new QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: (_) => data, - select: (_) => data, - )); - - final unsubscribe = observer.subscribe(); - - await sleep(10); - expect(observer.getCurrentResult()?.data, data); - - observer.setOptions(QueryObserverOptions( - queryKey: key, - queryFn: (_) => data, - structuralSharing: false, - select: (_) => selectedData, - )); - - await observer.refetch(filters: RefetchableQueryFilters(queryKey: key)); - expect(observer.getCurrentResult()?.data, selectedData); - unsubscribe(); - }); - - test('select function error using placeholderdata should log an error', () { - final key = queryKey(); - - QueryObserver, dynamic, Map, - Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: (_) => {"data": 'data'}, - placeholderData: {"data": 'placeholderdata'}, - select: (_) { - throw new Exception('error'); - }, - )); - - expect(true, true); - // expect(consoleMock).toHaveBeenNthCalledWith(1, new Error('error')) - - // consoleMock.mockRestore() - }); - - test( - 'should not use replaceEqualDeep for select value when structuralSharing option is true and placeholderdata is defined', - () { - final key = queryKey(); - - final data = {"value": 'data'}; - final selectedData1 = {"value": 'data'}; - final selectedData2 = {"value": 'data'}; - final placeholderData1 = {"value": 'data'}; - final placeholderData2 = {"value": 'data'}; - - final observer = new QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: (_) => data, - select: (_) => data, - )); - - observer.setOptions(QueryObserverOptions( - queryKey: key, - queryFn: (_) => data, - select: (_) { - return selectedData1; - }, - placeholderData: placeholderData1, - )); - observer.setOptions(QueryObserverOptions( - queryKey: key, - queryFn: (_) => data, - select: (_) { - return selectedData2; - }, - placeholderData: placeholderData2, - structuralSharing: false, - )); - - expect(observer.getCurrentResult()?.data, equals(selectedData2)); - }); - - test( - 'should not use an undefined value returned by select as placeholderdata', - () { - final key = queryKey(); - - final data = {"value": 'data'}; - final selectedData = {"value": 'data'}; - final placeholderData1 = {"value": 'data'}; - final placeholderData2 = {"value": 'data'}; - - final observer = new QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: (_) => data, - select: (_) => data, - )); - - observer.setOptions(QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: (_) => data, - select: (_) { - return selectedData; - }, - placeholderData: placeholderData1, - )); - - expect(observer.getCurrentResult()?.isPlaceholderData, isTrue); - - observer.setOptions(QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: (_) => data, - select: (_) { - return {}; - }, - placeholderData: placeholderData2, - )); - - expect(observer.getCurrentResult()?.isPlaceholderData, isFalse); - }); - - test( - 'updateResult should not notify cache listeners if cache option is false', - () async { - final key = queryKey(); - - final data1 = {"value": 'data 1'}; - final data2 = {"value": 'data 2'}; - - await queryClient.prefetchQuery(queryKey: key, queryFn: (_) => data1); - final observer = new QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>(queryKey: key)); - await queryClient.prefetchQuery(queryKey: key, queryFn: (_) => data2); - - final spy = SpyFn(); - final unsubscribe = queryClient.getQueryCache().subscribe(spy.fn1()); - observer.updateResult(NotifyOptions(cache: false)); - - expect(spy.calls, 0); - - unsubscribe(); - }); - - test( - 'should not notify observer when the stale timeout expires and the current result is stale', - () async { - final key = queryKey(); - final queryFn = (_) => {'data': "data"}; - - await queryClient.prefetchQuery(queryKey: key, queryFn: queryFn); - final observer = new QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: queryFn, - staleTime: Duration(milliseconds: 20), - )); - - final spy = SpyFn(); - final unsubscribe = observer.subscribe(spy.fn1()); - await queryClient.refetchQueries(queryKeys: key); - await sleep(10); - - // Force isStale to true - // because no use case has been found to reproduce this condition - // @ts-ignore - // observer.getCurrentResult().isStale = true; - await sleep(30); - expect(spy.calls, 0); - unsubscribe(); - }); - }); -} diff --git a/packages/fl_query/test/src/core/utils_test.dart b/packages/fl_query/test/src/core/utils_test.dart deleted file mode 100644 index 77aa934..0000000 --- a/packages/fl_query/test/src/core/utils_test.dart +++ /dev/null @@ -1,320 +0,0 @@ -import 'package:fl_query/src/core/utils.dart'; -import 'package:test/expect.dart'; -import 'package:test/scaffolding.dart'; - -void main() { - group('core/utils', () { - group('replaceEqualDeep', () { - test( - 'Should return the previous value When the next value is an equal primitive', - () { - expect(replaceEqualDeep(1, 1), equals(1)); - expect(replaceEqualDeep('1', '1'), equals('1')); - expect(replaceEqualDeep(true, true), true); - expect(replaceEqualDeep(false, false), false); - expect(replaceEqualDeep(null, null), null); - }); - test( - 'Should return the next value When the previous value is a different value', - () { - expect(replaceEqualDeep(1, 0), equals(0)); - expect(replaceEqualDeep(1, 2), equals(2)); - expect(replaceEqualDeep('1', '2'), equals('2')); - expect(replaceEqualDeep(true, false), equals(false)); - expect(replaceEqualDeep(false, true), equals(true)); - }); - - test( - 'Should return the next value When the previous value is a different type', - () { - final array = [1]; - final object = {"a": "a"}; - expect(replaceEqualDeep(0, null), equals(null)); - expect(replaceEqualDeep(null, 0), equals(0)); - expect(replaceEqualDeep(2, null), equals(null)); - expect(replaceEqualDeep(null, 2), equals(2)); - expect(replaceEqualDeep({}, null), equals(null)); - expect(replaceEqualDeep([], null), equals(null)); - expect(replaceEqualDeep(array, object), equals(object)); - expect(replaceEqualDeep(object, array), equals(array)); - }); - - test( - 'Should return the previous value When the next value is an equal array', - () { - final prev = [1, 2]; - final next = [1, 2]; - expect(replaceEqualDeep(prev, next), equals(prev)); - }); - - test( - 'Should return a copy When the previous value is a different array subset', - () { - final prev = [1, 2]; - final next = [1, 2, 3]; - final result = replaceEqualDeep(prev, next); - expect(result, equals(next)); - expect(result, isNot(equals(prev))); - }); - - test( - 'Should return the previous value When the next value is an equal empty array', - () { - final prev = []; - final next = []; - expect(replaceEqualDeep(prev, next), equals(prev)); - }); - - test( - 'Should return the previous value When the next value is an equal empty object', - () { - final prev = {}; - final next = {}; - expect(replaceEqualDeep(prev, next), equals(prev)); - }); - - test( - 'Should return the previous value When the next value is an equal object', - () { - final prev = {"a": 'a'}; - final next = {"a": 'a'}; - expect(replaceEqualDeep(prev, next), equals(prev)); - }); - - test('Should replace different values in objects', () { - final prev = { - "a": {"b": 'b'}, - "c": 'c' - }; - final next = { - "a": {"b": 'b'}, - "c": 'd' - }; - final result = replaceEqualDeep(prev, next); - expect(result, equals(next)); - expect(result, isNot(prev)); - expect(result["a"], equals(prev["a"])); - expect(result["c"], equals(next["c"])); - }); - - test('Should replace different values in arrays', () { - final prev = [ - 1, - {"a": 'a'}, - { - "b": {"b": 'b'} - }, - [1] - ]; - final next = [ - 1, - {"a": 'a'}, - { - "b": {"b": 'c'} - }, - [1] - ]; - final result = replaceEqualDeep(prev, next); - expect(result, equals(next)); - expect(result, isNot(prev)); - expect(result[0], prev[0]); - expect(result[1], prev[1]); - expect((result[2] as Map)["b"]["b"], (next[2] as Map)["b"]["b"]); - expect(result[3], prev[3]); - }); - - test( - 'Should replace different values in arrays When the next value is a subset', - () { - final prev = [ - {"a": 'a'}, - {"b": 'b'}, - {"c": 'c'} - ]; - final next = [ - {"a": 'a'}, - {"b": 'b'} - ]; - final result = replaceEqualDeep(prev, next); - expect(result, equals(next)); - expect(result, isNot(prev)); - expect(result[0], prev[0]); - expect(result[1], prev[1]); - expect(() => result[2], throwsRangeError); - }); - - test( - 'Should replace different values in arrays When the next value is a superset', - () { - final prev = [ - {"a": 'a'}, - {"b": 'b'} - ]; - final next = [ - {"a": 'a'}, - {"b": 'b'}, - {"c": 'c'} - ]; - final result = replaceEqualDeep(prev, next); - expect(result, equals(next)); - expect(result[0], equals(prev[0])); - expect(result[1], equals(prev[1])); - expect(result[2], equals(next[2])); - }); - - test('Should copy objects which are not arrays or objects', () { - final prev = [ - {"a": 'a'}, - {"b": 'b'}, - {"c": 'c'}, - 1 - ]; - final next = [ - {"a": 'a'}, - Map(), - {"c": 'c'}, - 2 - ]; - final result = replaceEqualDeep(prev, next); - expect(result[0], equals(prev[0])); - expect(result[1], equals(next[1])); - expect(result[2], equals(prev[2])); - expect(result[3], equals(next[3])); - }); - - test('Should support equal objects which are not arrays or objects', () { - final map = new Map(); - final prev = [ - map, - [1] - ]; - final next = [ - map, - [1] - ]; - final result = replaceEqualDeep(prev, next); - expect(result, equals(prev)); - }); - - test('Should support non equal objects which are not arrays or objects', - () { - final map1 = new Map(); - final map2 = new Map(); - final prev = [ - map1, - [1] - ]; - final next = [ - map2, - [1] - ]; - final result = replaceEqualDeep(prev, next); - expect(result[0], equals(next[0])); - expect(result[1], equals(prev[1])); - }); - - test('Should replace all parent objects if some nested value changes', - () { - final prev = { - "todo": { - "id": '1', - "meta": {"createdAt": 0}, - "state": {"done": false}, - }, - "otherTodo": { - "id": '2', - "meta": {"createdAt": 0}, - "state": {"done": true}, - }, - }; - final next = { - "todo": { - "id": '1', - "meta": {"createdAt": 0}, - "state": {"done": true}, - }, - "otherTodo": { - "id": '2', - "meta": {"createdAt": 0}, - "state": {"done": true}, - }, - }; - final result = replaceEqualDeep(prev, next); - expect(result, equals(next)); - expect(result["todo"] == prev["todo"], isFalse); - expect(result["todo"] == next["todo"], isFalse); - expect(result["todo"]["id"], equals((next["todo"] as Map)["id"])); - expect(result["todo"]["meta"], equals((prev["todo"] as Map)["meta"])); - expect( - result["todo"]["state"], - equals((next["todo"] as Map)["state"]), - ); - expect( - result["todo"]["state"]["done"], - (next["todo"] as Map)["state"]["done"], - ); - expect(result["otherTodo"], prev["otherTodo"]); - }); - - test('Should replace all parent arrays if some nested value changes', () { - final Map> prev = { - "todos": [ - { - "id": '1', - "meta": {"createdAt": 0}, - "state": {"done": false} - }, - { - "id": '2', - "meta": {"createdAt": 0}, - "state": {"done": true} - }, - ], - }; - final Map> next = { - "todos": [ - { - "id": '1', - "meta": {"createdAt": 0}, - "state": {"done": true} - }, - { - "id": '2', - "meta": {"createdAt": 0}, - "state": {"done": true} - }, - ], - }; - final result = replaceEqualDeep(prev, next); - expect(result, equals(next)); - expect(result["todos"][0], isNot(equals(prev["todos"]?.first))); - expect( - result["todos"][0]?["id"], - equals(next["todos"]?.first["id"]), - ); - expect( - result["todos"][0]?["meta"], - equals(prev["todos"]?.first["meta"]), - ); - expect(result["todos"][0]?["state"]["done"], - next["todos"]?.first["state"]["done"]); - expect(result["todos"][1], equals(prev["todos"]?[1])); - }); - }); - - group( - 'matchMutation', - () => { - // test('should return false if mutationKey options is undefined', () => { - // const filters = { mutationKey: 'key1' }; - // const queryClient = new QueryClient(); - // const mutation = new Mutation({ - // mutationId: 1, - // mutationCache: queryClient.getMutationCache(), - // options: {}, - // }) - // expect(matchMutation(filters, mutation)).toBeFalsy() - // }) - }); - }); -}