├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── android ├── .gitignore ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── src │ └── main │ └── AndroidManifest.xml ├── example ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── optimized │ │ │ │ │ └── cached │ │ │ │ │ └── image │ │ │ │ │ └── example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── .last_build_id │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Podfile.lock │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ └── contents.xcworkspacedata │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h ├── lib │ ├── main.dart │ ├── plugin_example │ │ ├── basic_content.dart │ │ ├── grid_content.dart │ │ └── list_content.dart │ └── template │ │ ├── globals.dart │ │ └── info_page.dart ├── linux │ ├── .gitignore │ ├── CMakeLists.txt │ ├── flutter │ │ └── CMakeLists.txt │ ├── main.cc │ ├── my_application.cc │ └── my_application.h ├── macos │ ├── .gitignore │ ├── Flutter │ │ ├── Flutter-Debug.xcconfig │ │ ├── Flutter-Release.xcconfig │ │ └── GeneratedPluginRegistrant.swift │ ├── Podfile │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── app_icon_1024.png │ │ │ ├── app_icon_128.png │ │ │ ├── app_icon_16.png │ │ │ ├── app_icon_256.png │ │ │ ├── app_icon_32.png │ │ │ ├── app_icon_512.png │ │ │ └── app_icon_64.png │ │ ├── Base.lproj │ │ └── MainMenu.xib │ │ ├── Configs │ │ ├── AppInfo.xcconfig │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── Warnings.xcconfig │ │ ├── DebugProfile.entitlements │ │ ├── Info.plist │ │ ├── MainFlutterWindow.swift │ │ └── Release.entitlements ├── pubspec.lock ├── pubspec.yaml ├── web │ ├── favicon.png │ ├── icons │ │ ├── Icon-192.png │ │ └── Icon-512.png │ ├── index.html │ └── manifest.json └── windows │ ├── .gitignore │ ├── CMakeLists.txt │ ├── flutter │ └── CMakeLists.txt │ └── runner │ ├── CMakeLists.txt │ ├── Runner.rc │ ├── flutter_window.cpp │ ├── flutter_window.h │ ├── main.cpp │ ├── resource.h │ ├── resources │ └── app_icon.ico │ ├── run_loop.cpp │ ├── run_loop.h │ ├── runner.exe.manifest │ ├── utils.cpp │ ├── utils.h │ ├── win32_window.cpp │ └── win32_window.h ├── ios ├── .gitignore ├── Assets │ └── .gitkeep └── optimized_cached_image.podspec ├── lib ├── optimized_cached_image.dart └── src │ ├── cache │ ├── default_image_cache_manager.dart │ └── image_cache_manager.dart │ ├── debug_tools.dart │ ├── image_provider │ ├── _image_provider_io.dart │ ├── _image_provider_web.dart │ ├── _load_async_web.dart │ ├── multi_image_stream_completer.dart │ └── optimized_cached_image_provider.dart │ ├── oci_widget.dart │ └── transformer │ ├── image_transformer.dart │ └── scale_info.dart ├── optimized_cached_image.iml ├── pubspec.lock ├── pubspec.yaml ├── screenshots └── streamed_vs_nonstreamed.jpg └── scripts └── checks.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | .idea/ 9 | doc/ 10 | /.flutter-plugins 11 | /.flutter-plugins-dependencies 12 | /example/linux/flutter/generated_plugin_registrant.cc 13 | /example/windows/flutter/generated_plugin_registrant.cc 14 | /example/linux/flutter/generated_plugin_registrant.h 15 | /example/windows/flutter/generated_plugin_registrant.h 16 | /example/linux/flutter/generated_plugins.cmake 17 | /example/windows/flutter/generated_plugins.cmake 18 | /android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java 19 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 0b8abb4724aa590dd0f429683339b1e045a1594d 8 | channel: stable 9 | 10 | project_type: plugin 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 3.0.1 2 | 3 | * Avoid unnecessary null checks 4 | 5 | ## 3.0.0 6 | 7 | * Null safety and interim gif support. 8 | 9 | ## 2.0.2-alpha 10 | 11 | * Limited gif support. Gifs are compressed into webp and rendered as of now. 12 | 13 | ## 2.0.1 14 | 15 | * Update to null safety package dependencies. OCI still needs to migrate code to respect null 16 | safety. Additionally fix issue with hero widgets 17 | 18 | ## 2.0.0 19 | 20 | * Update flutter cache manager dependency and other pub dependencies 21 | 22 | ## 2.0.0-dev.2 23 | 24 | * Fix issues with file package conflicting with dart io. 25 | 26 | ## 2.0.0-dev.1 27 | 28 | * Update with flutter cache manager dependency and `CachedNetworkImage`. Introduces breaking api 29 | changes. 30 | 31 | ## 1.0.0 32 | 33 | * Release to Pub 34 | 35 | ## 1.0.0-rc1 36 | 37 | * Removed included octo_image library. 38 | 39 | ## 1.0.0-beta 40 | 41 | * Prevent unnecessary downloads from happening by caching the image from the original url and 42 | resizing it for different sizes. 43 | 44 | ## 0.1.15 45 | 46 | * Syncing changes with Cached Network Image 47 | 48 | ## 0.1.14 49 | 50 | * Fix open socket issue in compression library and allow debug mode. 51 | 52 | ## 0.1.13 53 | 54 | * Fixed an issue around compression error handling 55 | 56 | ## 0.1.12 57 | 58 | * Fix issue with stream closure, causing some stream to remain open. 59 | 60 | ## 0.1.11 61 | 62 | * Revert to original file when compression fails. 63 | 64 | ## 0.1.10 65 | 66 | * Migrate to latest apis in cache manager dependency. Now the stream fetching is done by default 67 | instead of via flag. 68 | 69 | ## 0.1.9 70 | 71 | * Fix transparency/image format issues. 72 | 73 | ## 0.1.8 74 | 75 | * Fix dependency version breaking change in flutter cache library. 76 | 77 | ## 0.1.7 78 | 79 | * Add style fixes 80 | 81 | ## 0.1.6 82 | 83 | * Add experimental support for streamed downloading via `useHttpStream` flag which further reduces 84 | the memory footprint. 85 | 86 | ## 0.1.5 87 | 88 | * Minor lint issues and formatting patched. 89 | 90 | ## 0.1.4 91 | 92 | * Fixed issue faced while specifying custom width and height. 93 | 94 | ## 0.1.3 95 | 96 | * Readme updated. 97 | 98 | ## 0.1.2 99 | 100 | * Minor lint issues and formatting patched. 101 | 102 | ## 0.1.1 103 | 104 | * Updated examples. 105 | 106 | ## 0.1.0 107 | 108 | * Minimalist/Core functionality of the library and health fixes. 109 | 110 | ## 0.0.1 111 | 112 | * Minimalist/Core functionality of the library. 113 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2020 Anvith Bhat 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Optimized Cached Image 2 | 3 | [![pub package](https://img.shields.io/pub/v/optimized_cached_image.svg)](https://pub.dartlang.org/packages/optimized_cached_image) 4 | 5 | ### ❌❌ Important Update ❌❌ ### 6 | This library is no longer being maintained. When I started this library, it was meant to add memory performant extensions which could not be directly added into the parent library. Since then a lot has changed, [the parent library](https://github.com/Baseflow/flutter_cached_network_image) has incorporated similar changes, perhaps with the exception of a trivial `LayoutBuilder` that auto detects image sizes. I feel like this is a good time to deprecate this library in favour of the [parent](https://github.com/Baseflow/flutter_cached_network_image). 7 | 8 | ## Description 9 | A flutter library for loading images from network, resizing and caching them for memory sensitivity. 10 | This resizes and stores the images in cache based on parent container constraints and hence 11 | loads images of lower size into memory. This is heavily inspired by [cached_network_image](https://pub.dev/packages/cached_network_image) library. 12 | 13 | This library exposes two classes for loading images 14 | - `OptimizedCacheImage` which is a 1:1 mapping of `CachedNetworkImage`. 15 | - `OptimizedCacheImageProvider` which is a mapping of `CachedNetworkImageProvider`. 16 | 17 | ## How to use 18 | The OptimizedCacheImage can be used directly or through the ImageProvider. 19 | Both the OptimizedCacheImage as OptimizedCacheImageProvider have minimal support for web. It currently doesn't include caching. 20 | 21 | With a placeholder: 22 | ```dart 23 | OptimizedCacheImage( 24 | imageUrl: "http://via.placeholder.com/350x150", 25 | placeholder: (context, url) => CircularProgressIndicator(), 26 | errorWidget: (context, url, error) => Icon(Icons.error), 27 | ), 28 | ``` 29 | 30 | Or with a progress indicator: 31 | ```dart 32 | OptimizedCacheImage( 33 | imageUrl: "http://via.placeholder.com/350x150", 34 | progressIndicatorBuilder: (context, url, downloadProgress) => 35 | CircularProgressIndicator(value: downloadProgress.progress), 36 | errorWidget: (context, url, error) => Icon(Icons.error), 37 | ), 38 | ``` 39 | 40 | 41 | ````dart 42 | Image(image: OptimizedCacheImageProvider(url)) 43 | ```` 44 | 45 | When you want to have both the placeholder functionality and want to get the imageprovider to use in another widget you can provide an imageBuilder: 46 | ```dart 47 | OptimizedCacheImage( 48 | imageUrl: "http://via.placeholder.com/200x150", 49 | imageBuilder: (context, imageProvider) => Container( 50 | decoration: BoxDecoration( 51 | image: DecorationImage( 52 | image: imageProvider, 53 | fit: BoxFit.cover, 54 | colorFilter: 55 | ColorFilter.mode(Colors.red, BlendMode.colorBurn)), 56 | ), 57 | ), 58 | placeholder: (context, url) => CircularProgressIndicator(), 59 | errorWidget: (context, url, error) => Icon(Icons.error), 60 | ), 61 | ``` 62 | 63 | ## Handling Gifs 64 | OCI uses [Flutter Image Compress](https://pub.dev/packages/flutter_image_compress) as the compression library, while being memory efficient this library doesn't provide out of box support for gifs, however it does allow compressing to webp. Hence all gifs are compressed to webp format beginning `2.0.2-alpha`. 65 | 66 | ## How it works 67 | The optimized cached network images stores and retrieves files using the [flutter_cache_manager](https://pub.dev/packages/flutter_cache_manager). 68 | 69 | ## FAQ 70 | ### My app crashes when the image loading failed. (I know, this is not really a question.) 71 | Does it really crash though? The debugger might pause, as the Dart VM doesn't recognize it as a caught exception; the console might print errors; even your crash reporting tool might report it (I know, that really sucks). However, does it really crash? Probably everything is just running fine. If you really get an app crashes you are fine to report an issue, but do that with a small example so we can reproduce that crash. 72 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'anvith.in.optimized_cached_image' 2 | version '1.0-SNAPSHOT' 3 | 4 | buildscript { 5 | ext.kotlin_version = '1.6.21' 6 | repositories { 7 | google() 8 | jcenter() 9 | } 10 | 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:4.1.3' 13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 14 | } 15 | } 16 | 17 | rootProject.allprojects { 18 | repositories { 19 | google() 20 | jcenter() 21 | } 22 | } 23 | 24 | apply plugin: 'com.android.library' 25 | apply plugin: 'kotlin-android' 26 | 27 | android { 28 | compileSdkVersion 29 29 | 30 | sourceSets { 31 | main.java.srcDirs += 'src/main/kotlin' 32 | } 33 | defaultConfig { 34 | minSdkVersion 16 35 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 36 | } 37 | lintOptions { 38 | disable 'InvalidPackage' 39 | } 40 | } 41 | 42 | dependencies { 43 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 44 | } 45 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip 6 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'optimized_cached_image' 2 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | ios/.symlinks 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 0b8abb4724aa590dd0f429683339b1e045a1594d 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # optimized_cached_image_example 2 | 3 | Demonstrates how to use the optimized_cached_image plugin. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 31 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "optimized.cached.image" 42 | minSdkVersion 16 43 | targetSdkVersion 28 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 47 | } 48 | 49 | buildTypes { 50 | release { 51 | // TODO: Add your own signing config for the release build. 52 | // Signing with the debug keys for now, so `flutter run --release` works. 53 | signingConfig signingConfigs.debug 54 | } 55 | } 56 | } 57 | 58 | flutter { 59 | source '../..' 60 | } 61 | 62 | dependencies { 63 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 64 | testImplementation 'junit:junit:4.12' 65 | androidTestImplementation 'androidx.test:runner:1.1.1' 66 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 67 | } 68 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 12 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/optimized/cached/image/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package optimized.cached.image.example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity : FlutterActivity() -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/humblerookie/optimized_cached_image/c760d1d3a1b525cc1e71c93a808135d1f10d4d1e/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/humblerookie/optimized_cached_image/c760d1d3a1b525cc1e71c93a808135d1f10d4d1e/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/humblerookie/optimized_cached_image/c760d1d3a1b525cc1e71c93a808135d1f10d4d1e/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/humblerookie/optimized_cached_image/c760d1d3a1b525cc1e71c93a808135d1f10d4d1e/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/humblerookie/optimized_cached_image/c760d1d3a1b525cc1e71c93a808135d1f10d4d1e/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.6.21' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.1.3' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | .symlinks/ -------------------------------------------------------------------------------- /example/ios/Flutter/.last_build_id: -------------------------------------------------------------------------------- 1 | 7f50a9631ab41028892fbb0801436462 -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 9.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /example/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - flutter_image_compress (0.0.1): 4 | - Flutter 5 | - Mantle 6 | - SDWebImage 7 | - SDWebImageWebPCoder 8 | - FMDB (2.7.5): 9 | - FMDB/standard (= 2.7.5) 10 | - FMDB/standard (2.7.5) 11 | - libwebp (1.1.0): 12 | - libwebp/demux (= 1.1.0) 13 | - libwebp/mux (= 1.1.0) 14 | - libwebp/webp (= 1.1.0) 15 | - libwebp/demux (1.1.0): 16 | - libwebp/webp 17 | - libwebp/mux (1.1.0): 18 | - libwebp/demux 19 | - libwebp/webp (1.1.0) 20 | - Mantle (2.1.1): 21 | - Mantle/extobjc (= 2.1.1) 22 | - Mantle/extobjc (2.1.1) 23 | - path_provider_ios (0.0.1): 24 | - Flutter 25 | - SDWebImage (5.5.2): 26 | - SDWebImage/Core (= 5.5.2) 27 | - SDWebImage/Core (5.5.2) 28 | - SDWebImageWebPCoder (0.5.0): 29 | - libwebp (~> 1.0) 30 | - SDWebImage/Core (~> 5.5) 31 | - sqflite (0.0.2): 32 | - Flutter 33 | - FMDB (>= 2.7.5) 34 | - url_launcher_ios (0.0.1): 35 | - Flutter 36 | 37 | DEPENDENCIES: 38 | - Flutter (from `Flutter`) 39 | - flutter_image_compress (from `.symlinks/plugins/flutter_image_compress/ios`) 40 | - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) 41 | - sqflite (from `.symlinks/plugins/sqflite/ios`) 42 | - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) 43 | 44 | SPEC REPOS: 45 | trunk: 46 | - FMDB 47 | - libwebp 48 | - Mantle 49 | - SDWebImage 50 | - SDWebImageWebPCoder 51 | 52 | EXTERNAL SOURCES: 53 | Flutter: 54 | :path: Flutter 55 | flutter_image_compress: 56 | :path: ".symlinks/plugins/flutter_image_compress/ios" 57 | path_provider_ios: 58 | :path: ".symlinks/plugins/path_provider_ios/ios" 59 | sqflite: 60 | :path: ".symlinks/plugins/sqflite/ios" 61 | url_launcher_ios: 62 | :path: ".symlinks/plugins/url_launcher_ios/ios" 63 | 64 | SPEC CHECKSUMS: 65 | Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a 66 | flutter_image_compress: fd2b476345226e1a10ea352fa306af95704642c1 67 | FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a 68 | libwebp: 946cb3063cea9236285f7e9a8505d806d30e07f3 69 | Mantle: 35238ae6f2e2b2d474fa7b67fee82a59fea71915 70 | path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02 71 | SDWebImage: 4d5c027c935438f341ed33dbac53ff9f479922ca 72 | SDWebImageWebPCoder: e7ae855f058e3dcae99696920b6a5d134e9dcddf 73 | sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 74 | url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de 75 | 76 | PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c 77 | 78 | COCOAPODS: 1.11.3 79 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/humblerookie/optimized_cached_image/c760d1d3a1b525cc1e71c93a808135d1f10d4d1e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/humblerookie/optimized_cached_image/c760d1d3a1b525cc1e71c93a808135d1f10d4d1e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/humblerookie/optimized_cached_image/c760d1d3a1b525cc1e71c93a808135d1f10d4d1e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/humblerookie/optimized_cached_image/c760d1d3a1b525cc1e71c93a808135d1f10d4d1e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/humblerookie/optimized_cached_image/c760d1d3a1b525cc1e71c93a808135d1f10d4d1e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/humblerookie/optimized_cached_image/c760d1d3a1b525cc1e71c93a808135d1f10d4d1e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/humblerookie/optimized_cached_image/c760d1d3a1b525cc1e71c93a808135d1f10d4d1e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/humblerookie/optimized_cached_image/c760d1d3a1b525cc1e71c93a808135d1f10d4d1e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/humblerookie/optimized_cached_image/c760d1d3a1b525cc1e71c93a808135d1f10d4d1e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/humblerookie/optimized_cached_image/c760d1d3a1b525cc1e71c93a808135d1f10d4d1e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/humblerookie/optimized_cached_image/c760d1d3a1b525cc1e71c93a808135d1f10d4d1e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/humblerookie/optimized_cached_image/c760d1d3a1b525cc1e71c93a808135d1f10d4d1e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/humblerookie/optimized_cached_image/c760d1d3a1b525cc1e71c93a808135d1f10d4d1e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/humblerookie/optimized_cached_image/c760d1d3a1b525cc1e71c93a808135d1f10d4d1e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/humblerookie/optimized_cached_image/c760d1d3a1b525cc1e71c93a808135d1f10d4d1e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/humblerookie/optimized_cached_image/c760d1d3a1b525cc1e71c93a808135d1f10d4d1e/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/humblerookie/optimized_cached_image/c760d1d3a1b525cc1e71c93a808135d1f10d4d1e/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/humblerookie/optimized_cached_image/c760d1d3a1b525cc1e71c93a808135d1f10d4d1e/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | OCI Example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | CADisableMinimumFrameDurationOnPhone 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'template/globals.dart'; 4 | 5 | void main() { 6 | runApp(HumblerookiePluginExample()); 7 | } 8 | 9 | /// A Flutter application demonstrating the functionality of this plugin 10 | class HumblerookiePluginExample extends StatelessWidget { 11 | /// [MaterialColor] to be used in the app [ThemeData] 12 | final MaterialColor themeMaterialColor = 13 | createMaterialColor(const Color.fromRGBO(48, 49, 60, 1)); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return MaterialApp( 18 | title: 'Humblerookie $pluginName', 19 | theme: ThemeData( 20 | buttonTheme: ButtonThemeData( 21 | buttonColor: themeMaterialColor.shade500, 22 | disabledColor: themeMaterialColor.withRed(200), 23 | splashColor: themeMaterialColor.shade50, 24 | textTheme: ButtonTextTheme.primary, 25 | ), 26 | hintColor: themeMaterialColor.shade500, 27 | textTheme: TextTheme( 28 | bodyLarge: TextStyle( 29 | color: Colors.white, 30 | fontSize: 16, 31 | height: 1.3, 32 | ), 33 | bodyMedium: TextStyle( 34 | color: Colors.white, 35 | fontSize: 18, 36 | height: 1.2, 37 | ), 38 | labelLarge: TextStyle(color: Colors.white), 39 | displayLarge: TextStyle( 40 | color: Colors.white, 41 | fontSize: 18, 42 | ), 43 | ), 44 | visualDensity: VisualDensity.adaptivePlatformDensity, 45 | inputDecorationTheme: const InputDecorationTheme( 46 | fillColor: Color.fromRGBO(37, 37, 37, 1), 47 | filled: true, 48 | ), 49 | bottomAppBarTheme: 50 | BottomAppBarTheme(color: const Color.fromRGBO(57, 58, 71, 1)), 51 | colorScheme: ColorScheme.fromSwatch( 52 | primarySwatch: 53 | createMaterialColor(const Color.fromRGBO(48, 49, 60, 1))) 54 | .copyWith(background: const Color.fromRGBO(48, 49, 60, 0.8)) 55 | .copyWith(secondary: Colors.white60), 56 | ), 57 | home: AppHome(title: 'Baseflow $pluginName example app'), 58 | ); 59 | } 60 | 61 | /// Creates a [MaterialColor] based on the supplied [Color] 62 | static MaterialColor createMaterialColor(Color color) { 63 | List strengths = [.05]; 64 | Map swatch = {}; 65 | final r = color.red, g = color.green, b = color.blue; 66 | 67 | for (var i = 1; i < 10; i++) { 68 | strengths.add(0.1 * i); 69 | } 70 | for (var strength in strengths) { 71 | final ds = 0.5 - strength; 72 | swatch[(strength * 1000).round()] = Color.fromRGBO( 73 | r + ((ds < 0 ? r : (255 - r)) * ds).round(), 74 | g + ((ds < 0 ? g : (255 - g)) * ds).round(), 75 | b + ((ds < 0 ? b : (255 - b)) * ds).round(), 76 | 1, 77 | ); 78 | } 79 | return MaterialColor(color.value, swatch as Map); 80 | } 81 | } 82 | 83 | /// A Flutter example demonstrating how the [pluginName] plugin could be used 84 | class AppHome extends StatefulWidget { 85 | /// Constructs the [AppHome] class 86 | AppHome({Key? key, this.title}) : super(key: key); 87 | 88 | /// The [title] of the application, which is shown in the application's 89 | /// title bar. 90 | final String? title; 91 | 92 | @override 93 | _AppHomeState createState() => _AppHomeState(); 94 | } 95 | 96 | class _AppHomeState extends State { 97 | static final PageController _pageController = PageController(initialPage: 0); 98 | int _currentPage = 0; 99 | 100 | @override 101 | Widget build(BuildContext context) { 102 | return Scaffold( 103 | appBar: AppBar( 104 | backgroundColor: Theme.of(context).bottomAppBarTheme.color, 105 | title: Center( 106 | child: Text("OptimizedCachedImage"), 107 | ), 108 | ), 109 | backgroundColor: Theme.of(context).colorScheme.background, 110 | body: PageView( 111 | controller: _pageController, 112 | children: pages, 113 | onPageChanged: (page) { 114 | setState(() { 115 | _currentPage = page; 116 | }); 117 | }, 118 | ), 119 | bottomNavigationBar: _bottomAppBar(), 120 | ); 121 | } 122 | 123 | BottomAppBar _bottomAppBar() { 124 | return BottomAppBar( 125 | elevation: 5, 126 | color: Theme.of(context).bottomAppBarTheme.color, 127 | child: Row( 128 | mainAxisSize: MainAxisSize.max, 129 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 130 | children: List.unmodifiable(() sync* { 131 | for (var i = 0; i < pages.length; i++) { 132 | yield Expanded( 133 | child: IconButton( 134 | iconSize: 30, 135 | icon: Icon(icons.elementAt(i)), 136 | color: _bottomAppBarIconColor(i), 137 | onPressed: () => _animateToPage(i), 138 | ), 139 | ); 140 | } 141 | }()), 142 | ), 143 | ); 144 | } 145 | 146 | void _animateToPage(int page) { 147 | _pageController.animateToPage(page, 148 | duration: const Duration(milliseconds: 200), curve: Curves.linear); 149 | } 150 | 151 | Color _bottomAppBarIconColor(int page) { 152 | return _currentPage == page 153 | ? Colors.white 154 | : Theme.of(context).colorScheme.secondary; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /example/lib/plugin_example/basic_content.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_blurhash/flutter_blurhash.dart'; 3 | import 'package:optimized_cached_image/optimized_cached_image.dart'; 4 | 5 | class BasicContent extends StatelessWidget { 6 | @override 7 | Widget build(BuildContext context) { 8 | return SingleChildScrollView( 9 | child: Center( 10 | child: Column( 11 | mainAxisAlignment: MainAxisAlignment.center, 12 | children: [ 13 | _blurHashImage(), 14 | _sizedContainer( 15 | const Image( 16 | image: OptimizedCacheImageProvider( 17 | "https://sample-videos.com/gif/3.gif", 18 | ), 19 | ), 20 | ), 21 | _sizedContainer( 22 | OptimizedCacheImage( 23 | progressIndicatorBuilder: (context, url, progress) => Center( 24 | child: CircularProgressIndicator( 25 | value: progress.progress, 26 | ), 27 | ), 28 | imageUrl: 29 | 'https://images.unsplash.com/photo-1532264523420-881a47db012d?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9', 30 | ), 31 | ), 32 | _sizedContainer( 33 | OptimizedCacheImage( 34 | placeholder: (context, url) => 35 | const CircularProgressIndicator(), 36 | imageUrl: 'https://via.placeholder.com/200x150', 37 | ), 38 | ), 39 | _sizedContainer( 40 | OptimizedCacheImage( 41 | imageUrl: 'https://via.placeholder.com/300x150', 42 | imageBuilder: (context, imageProvider) => Container( 43 | decoration: BoxDecoration( 44 | image: DecorationImage( 45 | image: imageProvider, 46 | fit: BoxFit.cover, 47 | colorFilter: const ColorFilter.mode( 48 | Colors.red, 49 | BlendMode.colorBurn, 50 | ), 51 | ), 52 | ), 53 | ), 54 | placeholder: (context, url) => 55 | const CircularProgressIndicator(), 56 | errorWidget: (context, url, error) => const Icon(Icons.error), 57 | ), 58 | ), 59 | OptimizedCacheImage( 60 | imageUrl: 'https://via.placeholder.com/300x300', 61 | placeholder: (context, url) => const CircleAvatar( 62 | backgroundColor: Colors.amber, 63 | radius: 150, 64 | ), 65 | imageBuilder: (context, image) => CircleAvatar( 66 | backgroundImage: image, 67 | radius: 150, 68 | ), 69 | ), 70 | _sizedContainer( 71 | OptimizedCacheImage( 72 | imageUrl: 'https://notAvalid.uri', 73 | placeholder: (context, url) => 74 | const CircularProgressIndicator(), 75 | errorWidget: (context, url, error) => const Icon(Icons.error), 76 | ), 77 | ), 78 | _sizedContainer( 79 | OptimizedCacheImage( 80 | imageUrl: 'not a uri at all', 81 | placeholder: (context, url) => 82 | const CircularProgressIndicator(), 83 | errorWidget: (context, url, error) => const Icon(Icons.error), 84 | ), 85 | ), 86 | _sizedContainer( 87 | OptimizedCacheImage( 88 | maxHeightDiskCache: 10, 89 | imageUrl: 'https://via.placeholder.com/350x200', 90 | placeholder: (context, url) => 91 | const CircularProgressIndicator(), 92 | errorWidget: (context, url, error) => const Icon(Icons.error), 93 | fadeOutDuration: const Duration(seconds: 1), 94 | fadeInDuration: const Duration(seconds: 3), 95 | ), 96 | ), 97 | ], 98 | ), 99 | ), 100 | ); 101 | } 102 | 103 | Widget _blurHashImage() { 104 | return SizedBox( 105 | width: double.infinity, 106 | child: OptimizedCacheImage( 107 | placeholder: (context, url) => const AspectRatio( 108 | aspectRatio: 1.6, 109 | child: BlurHash(hash: 'LEHV6nWB2yk8pyo0adR*.7kCMdnj'), 110 | ), 111 | imageUrl: 'https://blurha.sh/assets/images/img1.jpg', 112 | fit: BoxFit.cover, 113 | ), 114 | ); 115 | } 116 | 117 | Widget _sizedContainer(Widget child) { 118 | return SizedBox( 119 | width: 300.0, 120 | height: 150.0, 121 | child: Center(child: child), 122 | ); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /example/lib/plugin_example/grid_content.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:optimized_cached_image/optimized_cached_image.dart'; 3 | 4 | /// Demonstrates a [GridView] containing [OptimizedCacheImage] 5 | class GridContent extends StatelessWidget { 6 | @override 7 | Widget build(BuildContext context) { 8 | return GridView.builder( 9 | itemCount: 10, 10 | gridDelegate: 11 | const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3), 12 | itemBuilder: (BuildContext context, int index) => OptimizedCacheImage( 13 | imageUrl: 'https://loremflickr.com/100/100/music?lock=$index', 14 | placeholder: _loader, 15 | errorWidget: _error, 16 | ), 17 | ); 18 | } 19 | 20 | Widget _loader(BuildContext context, String url) { 21 | return const Center( 22 | child: CircularProgressIndicator(), 23 | ); 24 | } 25 | 26 | Widget _error(BuildContext context, String url, dynamic error) { 27 | print(error); 28 | return const Center(child: Icon(Icons.error)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /example/lib/plugin_example/list_content.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:optimized_cached_image/optimized_cached_image.dart'; 3 | 4 | /// Demonstrates a [ListView] containing [OptimizedCacheImage] 5 | class ListContent extends StatelessWidget { 6 | @override 7 | Widget build(BuildContext context) { 8 | return ListView.builder( 9 | itemBuilder: (BuildContext context, int index) => Card( 10 | child: Column( 11 | children: [ 12 | OptimizedCacheImage( 13 | imageUrl: 'https://loremflickr.com/320/240/music?lock=$index', 14 | placeholder: (BuildContext context, String url) => Container( 15 | width: 320, 16 | height: 240, 17 | color: Colors.purple, 18 | ), 19 | ), 20 | ], 21 | ), 22 | ), 23 | itemCount: 10, 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /example/lib/template/globals.dart: -------------------------------------------------------------------------------- 1 | import 'dart:core'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | import '../plugin_example/basic_content.dart'; 6 | import '../plugin_example/grid_content.dart'; 7 | import '../plugin_example/list_content.dart'; 8 | import 'info_page.dart'; 9 | 10 | /// The name of the plugin, which will be displayed throughout the example App. 11 | const String pluginName = 'OptimizedCachedImage'; 12 | 13 | /// Returns Github URL, which is shown in the [InfoPage]. 14 | const String githubURL = 15 | 'https://github.com/humblerookie/optimized_cached_image'; 16 | 17 | /// Returns Baseflow URL, which is shown in the [InfoPage]. 18 | const String baseflowURL = 'https://anvith.dev'; 19 | 20 | /// Returns pub.dev URL, which is shown in the [InfoPage]. 21 | const String pubDevURL = 'https://pub.dev/packages/optimized_cached_image'; 22 | 23 | /// [EdgeInsets] to define horizontal padding throughout the application. 24 | const EdgeInsets defaultHorizontalPadding = 25 | EdgeInsets.symmetric(horizontal: 24); 26 | 27 | /// [EdgeInsets] to define vertical padding throughout the application. 28 | const EdgeInsets defaultVerticalPadding = EdgeInsets.symmetric(vertical: 24); 29 | 30 | /// Returns a [List] with [IconData] to show in the [AppHome] [AppBar]. 31 | final List icons = [ 32 | Icons.image, 33 | Icons.list, 34 | Icons.grid_on, 35 | Icons.info_outline, 36 | ]; 37 | 38 | /// Returns a [List] with [Widget]s to construct pages in the [AppBar]. 39 | final List pages = [ 40 | BasicContent(), 41 | ListContent(), 42 | GridContent(), 43 | InfoPage(), 44 | ]; 45 | -------------------------------------------------------------------------------- /example/lib/template/info_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:url_launcher/url_launcher.dart'; 3 | 4 | import 'globals.dart'; 5 | 6 | /// [StatelessWidget] displaying information about Baseflow 7 | class InfoPage extends StatelessWidget { 8 | @override 9 | Widget build(BuildContext context) { 10 | return SizedBox.expand( 11 | child: Align( 12 | alignment: Alignment.bottomCenter, 13 | child: SingleChildScrollView( 14 | child: Padding( 15 | padding: defaultHorizontalPadding + defaultVerticalPadding, 16 | child: Column( 17 | mainAxisSize: MainAxisSize.max, 18 | mainAxisAlignment: MainAxisAlignment.end, 19 | children: [ 20 | Align( 21 | alignment: Alignment.centerLeft, 22 | child: Text("OptimizedCachedImage by humblerookie"), 23 | ), 24 | const Padding( 25 | padding: EdgeInsets.symmetric(vertical: 24), 26 | ), 27 | Text( 28 | 'This app showcases the possibilities of the $pluginName ' 29 | 'plugin. ' 30 | 'This plugin is available as open source project on Github. ' 31 | '\n\n' 32 | 'Need help with integrating functionalities within your own ' 33 | 'apps? Contact us at anvithv4@gmail.com', 34 | style: Theme.of(context).textTheme.bodyLarge, 35 | ), 36 | const Padding( 37 | padding: EdgeInsets.symmetric(vertical: 8), 38 | ), 39 | _launcherRaisedButton( 40 | 'Find us on Github', 41 | githubURL, 42 | context, 43 | ), 44 | _launcherRaisedButton( 45 | 'Find us on pub.dev', 46 | pubDevURL, 47 | context, 48 | ), 49 | _launcherRaisedButton( 50 | 'Visit anvith.dev', 51 | baseflowURL, 52 | context, 53 | ), 54 | const Padding( 55 | padding: EdgeInsets.only(bottom: 30), 56 | ), 57 | ], 58 | ), 59 | ), 60 | ), 61 | ), 62 | ); 63 | } 64 | 65 | Widget _launcherRaisedButton(String text, String url, BuildContext context) { 66 | return Container( 67 | width: MediaQuery.of(context).size.width, 68 | height: 50, 69 | margin: const EdgeInsets.only(top: 24.0), 70 | alignment: Alignment.center, 71 | child: SizedBox.expand( 72 | child: ElevatedButton( 73 | child: Text(text), 74 | onPressed: () => _launchURL(url), 75 | ), 76 | ), 77 | ); 78 | } 79 | 80 | Future _launchURL(String url) async { 81 | final uri = Uri.tryParse(url); 82 | if (uri != null) { 83 | await launchUrl(uri); 84 | } else { 85 | throw 'Could not launch $url'; 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /example/linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /example/linux/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(runner LANGUAGES CXX) 3 | 4 | set(BINARY_NAME "example") 5 | set(APPLICATION_ID "com.example.example") 6 | 7 | cmake_policy(SET CMP0063 NEW) 8 | 9 | set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") 10 | 11 | # Configure build options. 12 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 13 | set(CMAKE_BUILD_TYPE "Debug" CACHE 14 | STRING "Flutter build mode" FORCE) 15 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 16 | "Debug" "Profile" "Release") 17 | endif() 18 | 19 | # Compilation settings that should be applied to most targets. 20 | function(APPLY_STANDARD_SETTINGS TARGET) 21 | target_compile_features(${TARGET} PUBLIC cxx_std_14) 22 | target_compile_options(${TARGET} PRIVATE -Wall -Werror) 23 | target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") 24 | target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") 25 | endfunction() 26 | 27 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 28 | 29 | # Flutter library and tool build rules. 30 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 31 | 32 | # System-level dependencies. 33 | find_package(PkgConfig REQUIRED) 34 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 35 | 36 | add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") 37 | 38 | # Application build 39 | add_executable(${BINARY_NAME} 40 | "main.cc" 41 | "my_application.cc" 42 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 43 | ) 44 | apply_standard_settings(${BINARY_NAME}) 45 | target_link_libraries(${BINARY_NAME} PRIVATE flutter) 46 | target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) 47 | add_dependencies(${BINARY_NAME} flutter_assemble) 48 | # Only the install-generated bundle's copy of the executable will launch 49 | # correctly, since the resources must in the right relative locations. To avoid 50 | # people trying to run the unbundled copy, put it in a subdirectory instead of 51 | # the default top-level location. 52 | set_target_properties(${BINARY_NAME} 53 | PROPERTIES 54 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" 55 | ) 56 | 57 | # Generated plugin build rules, which manage building the plugins and adding 58 | # them to the application. 59 | include(flutter/generated_plugins.cmake) 60 | 61 | 62 | # === Installation === 63 | # By default, "installing" just makes a relocatable bundle in the build 64 | # directory. 65 | set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") 66 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 67 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 68 | endif() 69 | 70 | # Start with a clean build bundle directory every time. 71 | install(CODE " 72 | file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") 73 | " COMPONENT Runtime) 74 | 75 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 76 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") 77 | 78 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 79 | COMPONENT Runtime) 80 | 81 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 82 | COMPONENT Runtime) 83 | 84 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 85 | COMPONENT Runtime) 86 | 87 | if(PLUGIN_BUNDLED_LIBRARIES) 88 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" 89 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 90 | COMPONENT Runtime) 91 | endif() 92 | 93 | # Fully re-copy the assets directory on each build to avoid having stale files 94 | # from a previous install. 95 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 96 | install(CODE " 97 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 98 | " COMPONENT Runtime) 99 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 100 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 101 | 102 | # Install the AOT library on non-Debug builds only. 103 | if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") 104 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 105 | COMPONENT Runtime) 106 | endif() 107 | -------------------------------------------------------------------------------- /example/linux/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 4 | 5 | # Configuration provided via flutter tool. 6 | include(${EPHEMERAL_DIR}/generated_config.cmake) 7 | 8 | # TODO: Move the rest of this into files in ephemeral. See 9 | # https://github.com/flutter/flutter/issues/57146. 10 | 11 | # Serves the same purpose as list(TRANSFORM ... PREPEND ...), 12 | # which isn't available in 3.10. 13 | function(list_prepend LIST_NAME PREFIX) 14 | set(NEW_LIST "") 15 | foreach(element ${${LIST_NAME}}) 16 | list(APPEND NEW_LIST "${PREFIX}${element}") 17 | endforeach(element) 18 | set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) 19 | endfunction() 20 | 21 | # === Flutter Library === 22 | # System-level dependencies. 23 | find_package(PkgConfig REQUIRED) 24 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 25 | pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) 26 | pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) 27 | pkg_check_modules(BLKID REQUIRED IMPORTED_TARGET blkid) 28 | 29 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") 30 | 31 | # Published to parent scope for install step. 32 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 33 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 34 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 35 | set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) 36 | 37 | list(APPEND FLUTTER_LIBRARY_HEADERS 38 | "fl_basic_message_channel.h" 39 | "fl_binary_codec.h" 40 | "fl_binary_messenger.h" 41 | "fl_dart_project.h" 42 | "fl_engine.h" 43 | "fl_json_message_codec.h" 44 | "fl_json_method_codec.h" 45 | "fl_message_codec.h" 46 | "fl_method_call.h" 47 | "fl_method_channel.h" 48 | "fl_method_codec.h" 49 | "fl_method_response.h" 50 | "fl_plugin_registrar.h" 51 | "fl_plugin_registry.h" 52 | "fl_standard_message_codec.h" 53 | "fl_standard_method_codec.h" 54 | "fl_string_codec.h" 55 | "fl_value.h" 56 | "fl_view.h" 57 | "flutter_linux.h" 58 | ) 59 | list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") 60 | add_library(flutter INTERFACE) 61 | target_include_directories(flutter INTERFACE 62 | "${EPHEMERAL_DIR}" 63 | ) 64 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") 65 | target_link_libraries(flutter INTERFACE 66 | PkgConfig::GTK 67 | PkgConfig::GLIB 68 | PkgConfig::GIO 69 | PkgConfig::BLKID 70 | ) 71 | add_dependencies(flutter flutter_assemble) 72 | 73 | # === Flutter tool backend === 74 | # _phony_ is a non-existent file to force this command to run every time, 75 | # since currently there's no way to get a full input/output list from the 76 | # flutter tool. 77 | add_custom_command( 78 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 79 | ${CMAKE_CURRENT_BINARY_DIR}/_phony_ 80 | COMMAND ${CMAKE_COMMAND} -E env 81 | ${FLUTTER_TOOL_ENVIRONMENT} 82 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" 83 | linux-x64 ${CMAKE_BUILD_TYPE} 84 | ) 85 | add_custom_target(flutter_assemble DEPENDS 86 | "${FLUTTER_LIBRARY}" 87 | ${FLUTTER_LIBRARY_HEADERS} 88 | ) 89 | -------------------------------------------------------------------------------- /example/linux/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | // Only X11 is currently supported. 5 | // Wayland support is being developed: https://github.com/flutter/flutter/issues/57932. 6 | gdk_set_allowed_backends("x11"); 7 | 8 | g_autoptr(MyApplication) app = my_application_new(); 9 | return g_application_run(G_APPLICATION(app), argc, argv); 10 | } 11 | -------------------------------------------------------------------------------- /example/linux/my_application.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | struct _MyApplication { 8 | GtkApplication parent_instance; 9 | }; 10 | 11 | G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) 12 | 13 | // Implements GApplication::activate. 14 | static void my_application_activate(GApplication* application) { 15 | GtkWindow* window = 16 | GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); 17 | GtkHeaderBar *header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); 18 | gtk_widget_show(GTK_WIDGET(header_bar)); 19 | gtk_header_bar_set_title(header_bar, "example"); 20 | gtk_header_bar_set_show_close_button(header_bar, TRUE); 21 | gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); 22 | gtk_window_set_default_size(window, 1280, 720); 23 | gtk_widget_show(GTK_WIDGET(window)); 24 | 25 | g_autoptr(FlDartProject) project = fl_dart_project_new(); 26 | 27 | FlView* view = fl_view_new(project); 28 | gtk_widget_show(GTK_WIDGET(view)); 29 | gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); 30 | 31 | fl_register_plugins(FL_PLUGIN_REGISTRY(view)); 32 | 33 | gtk_widget_grab_focus(GTK_WIDGET(view)); 34 | } 35 | 36 | static void my_application_class_init(MyApplicationClass* klass) { 37 | G_APPLICATION_CLASS(klass)->activate = my_application_activate; 38 | } 39 | 40 | static void my_application_init(MyApplication* self) {} 41 | 42 | MyApplication* my_application_new() { 43 | return MY_APPLICATION(g_object_new(my_application_get_type(), 44 | "application-id", APPLICATION_ID, 45 | nullptr)); 46 | } 47 | -------------------------------------------------------------------------------- /example/linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /example/macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/xcuserdata/ 7 | -------------------------------------------------------------------------------- /example/macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import path_provider_macos 9 | import sqflite 10 | import url_launcher_macos 11 | 12 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 13 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 14 | SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) 15 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) 16 | } 17 | -------------------------------------------------------------------------------- /example/macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.11' 2 | 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 | 6 | project 'Runner', { 7 | 'Debug' => :debug, 8 | 'Profile' => :release, 9 | 'Release' => :release, 10 | } 11 | 12 | def parse_KV_file(file, separator='=') 13 | file_abs_path = File.expand_path(file) 14 | if !File.exists? file_abs_path 15 | return []; 16 | end 17 | pods_ary = [] 18 | skip_line_start_symbols = ["#", "/"] 19 | File.foreach(file_abs_path) { |line| 20 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 21 | plugin = line.split(pattern=separator) 22 | if plugin.length == 2 23 | podname = plugin[0].strip() 24 | path = plugin[1].strip() 25 | podpath = File.expand_path("#{path}", file_abs_path) 26 | pods_ary.push({:name => podname, :path => podpath}); 27 | else 28 | puts "Invalid plugin specification: #{line}" 29 | end 30 | } 31 | return pods_ary 32 | end 33 | 34 | def pubspec_supports_macos(file) 35 | file_abs_path = File.expand_path(file) 36 | if !File.exists? file_abs_path 37 | return false; 38 | end 39 | File.foreach(file_abs_path) { |line| 40 | return true if line =~ /^\s*macos:/ 41 | } 42 | return false 43 | end 44 | 45 | target 'Runner' do 46 | use_frameworks! 47 | use_modular_headers! 48 | 49 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 50 | # referring to absolute paths on developers' machines. 51 | ephemeral_dir = File.join('Flutter', 'ephemeral') 52 | symlink_dir = File.join(ephemeral_dir, '.symlinks') 53 | symlink_plugins_dir = File.join(symlink_dir, 'plugins') 54 | system("rm -rf #{symlink_dir}") 55 | system("mkdir -p #{symlink_plugins_dir}") 56 | 57 | # Flutter Pods 58 | generated_xcconfig = parse_KV_file(File.join(ephemeral_dir, 'Flutter-Generated.xcconfig')) 59 | if generated_xcconfig.empty? 60 | puts "Flutter-Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first." 61 | end 62 | generated_xcconfig.map { |p| 63 | if p[:name] == 'FLUTTER_FRAMEWORK_DIR' 64 | symlink = File.join(symlink_dir, 'flutter') 65 | File.symlink(File.dirname(p[:path]), symlink) 66 | pod 'FlutterMacOS', :path => File.join(symlink, File.basename(p[:path])) 67 | end 68 | } 69 | 70 | # Plugin Pods 71 | plugin_pods = parse_KV_file('../.flutter-plugins') 72 | plugin_pods.map { |p| 73 | symlink = File.join(symlink_plugins_dir, p[:name]) 74 | File.symlink(p[:path], symlink) 75 | if pubspec_supports_macos(File.join(symlink, 'pubspec.yaml')) 76 | pod p[:name], :path => File.join(symlink, 'macos') 77 | end 78 | } 79 | end 80 | 81 | # Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. 82 | install! 'cocoapods', :disable_input_output_paths => true 83 | -------------------------------------------------------------------------------- /example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /example/macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @NSApplicationMain 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "app_icon_16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "app_icon_32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "app_icon_32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "app_icon_64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "app_icon_128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "app_icon_256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "app_icon_256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "app_icon_512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "app_icon_512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "app_icon_1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/humblerookie/optimized_cached_image/c760d1d3a1b525cc1e71c93a808135d1f10d4d1e/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/humblerookie/optimized_cached_image/c760d1d3a1b525cc1e71c93a808135d1f10d4d1e/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/humblerookie/optimized_cached_image/c760d1d3a1b525cc1e71c93a808135d1f10d4d1e/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/humblerookie/optimized_cached_image/c760d1d3a1b525cc1e71c93a808135d1f10d4d1e/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/humblerookie/optimized_cached_image/c760d1d3a1b525cc1e71c93a808135d1f10d4d1e/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/humblerookie/optimized_cached_image/c760d1d3a1b525cc1e71c93a808135d1f10d4d1e/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/humblerookie/optimized_cached_image/c760d1d3a1b525cc1e71c93a808135d1f10d4d1e/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /example/macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = example 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2020 com.example. All rights reserved. 15 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /example/macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.server 10 | 11 | com.apple.security.network.client 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /example/macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController.init() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example/macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | archive: 5 | dependency: transitive 6 | description: 7 | name: archive 8 | sha256: eb33140ede1b4039f4ad631f7bf3cfa58e24514e8bf87184bc32f17541af87fc 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "3.3.0" 12 | async: 13 | dependency: transitive 14 | description: 15 | name: async 16 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "2.11.0" 20 | boolean_selector: 21 | dependency: transitive 22 | description: 23 | name: boolean_selector 24 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "2.1.1" 28 | characters: 29 | dependency: transitive 30 | description: 31 | name: characters 32 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "1.3.0" 36 | clock: 37 | dependency: transitive 38 | description: 39 | name: clock 40 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "1.1.1" 44 | collection: 45 | dependency: transitive 46 | description: 47 | name: collection 48 | sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "1.17.1" 52 | cross_file: 53 | dependency: transitive 54 | description: 55 | name: cross_file 56 | sha256: "0b0036e8cccbfbe0555fd83c1d31a6f30b77a96b598b35a5d36dd41f718695e9" 57 | url: "https://pub.dev" 58 | source: hosted 59 | version: "0.3.3+4" 60 | crypto: 61 | dependency: transitive 62 | description: 63 | name: crypto 64 | sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67 65 | url: "https://pub.dev" 66 | source: hosted 67 | version: "3.0.2" 68 | cupertino_icons: 69 | dependency: "direct main" 70 | description: 71 | name: cupertino_icons 72 | sha256: "1989d917fbe8e6b39806207df5a3fdd3d816cbd090fac2ce26fb45e9a71476e5" 73 | url: "https://pub.dev" 74 | source: hosted 75 | version: "1.0.4" 76 | fake_async: 77 | dependency: transitive 78 | description: 79 | name: fake_async 80 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" 81 | url: "https://pub.dev" 82 | source: hosted 83 | version: "1.3.1" 84 | ffi: 85 | dependency: transitive 86 | description: 87 | name: ffi 88 | sha256: "13a6ccf6a459a125b3fcdb6ec73bd5ff90822e071207c663bfd1f70062d51d18" 89 | url: "https://pub.dev" 90 | source: hosted 91 | version: "1.2.1" 92 | file: 93 | dependency: "direct main" 94 | description: 95 | name: file 96 | sha256: b69516f2c26a5bcac4eee2e32512e1a5205ab312b3536c1c1227b2b942b5f9ad 97 | url: "https://pub.dev" 98 | source: hosted 99 | version: "6.1.2" 100 | flutter: 101 | dependency: "direct main" 102 | description: flutter 103 | source: sdk 104 | version: "0.0.0" 105 | flutter_blurhash: 106 | dependency: transitive 107 | description: 108 | name: flutter_blurhash 109 | sha256: "05001537bd3fac7644fa6558b09ec8c0a3f2eba78c0765f88912882b1331a5c6" 110 | url: "https://pub.dev" 111 | source: hosted 112 | version: "0.7.0" 113 | flutter_cache_manager: 114 | dependency: transitive 115 | description: 116 | name: flutter_cache_manager 117 | sha256: "32cd900555219333326a2d0653aaaf8671264c29befa65bbd9856d204a4c9fb3" 118 | url: "https://pub.dev" 119 | source: hosted 120 | version: "3.3.0" 121 | flutter_image_compress: 122 | dependency: transitive 123 | description: 124 | name: flutter_image_compress 125 | sha256: "2babae2c69ee4849c3d3ae0f1f10c14a6b8132390e4583c5d3b1f02e8167a022" 126 | url: "https://pub.dev" 127 | source: hosted 128 | version: "2.0.3" 129 | flutter_image_compress_common: 130 | dependency: transitive 131 | description: 132 | name: flutter_image_compress_common 133 | sha256: "88aae2219cd4507992643f19aee7882fe8ff82375ffa8cb4c876e3bfe3e7c3b6" 134 | url: "https://pub.dev" 135 | source: hosted 136 | version: "1.0.1" 137 | flutter_image_compress_platform_interface: 138 | dependency: transitive 139 | description: 140 | name: flutter_image_compress_platform_interface 141 | sha256: dae0a3eb1fb7f38cf327cc2005dc287bc891becdd83c519277de4415fd9e065d 142 | url: "https://pub.dev" 143 | source: hosted 144 | version: "1.0.1" 145 | flutter_image_compress_web: 146 | dependency: transitive 147 | description: 148 | name: flutter_image_compress_web 149 | sha256: "677f02509bdf5fd71afb560a22f0444e82c9ee887d223cd006cb44fd145fcb17" 150 | url: "https://pub.dev" 151 | source: hosted 152 | version: "0.1.3" 153 | flutter_test: 154 | dependency: "direct dev" 155 | description: flutter 156 | source: sdk 157 | version: "0.0.0" 158 | flutter_web_plugins: 159 | dependency: transitive 160 | description: flutter 161 | source: sdk 162 | version: "0.0.0" 163 | http: 164 | dependency: transitive 165 | description: 166 | name: http 167 | sha256: "2ed163531e071c2c6b7c659635112f24cb64ecbebf6af46b550d536c0b1aa112" 168 | url: "https://pub.dev" 169 | source: hosted 170 | version: "0.13.4" 171 | http_parser: 172 | dependency: transitive 173 | description: 174 | name: http_parser 175 | sha256: db3060f22889f3d9d55f6a217565486737037eec3609f7f3eca4d0c67ee0d8a0 176 | url: "https://pub.dev" 177 | source: hosted 178 | version: "4.0.1" 179 | image: 180 | dependency: transitive 181 | description: 182 | name: image 183 | sha256: a72242c9a0ffb65d03de1b7113bc4e189686fc07c7147b8b41811d0dd0e0d9bf 184 | url: "https://pub.dev" 185 | source: hosted 186 | version: "4.0.17" 187 | js: 188 | dependency: transitive 189 | description: 190 | name: js 191 | sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 192 | url: "https://pub.dev" 193 | source: hosted 194 | version: "0.6.7" 195 | matcher: 196 | dependency: transitive 197 | description: 198 | name: matcher 199 | sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" 200 | url: "https://pub.dev" 201 | source: hosted 202 | version: "0.12.15" 203 | material_color_utilities: 204 | dependency: transitive 205 | description: 206 | name: material_color_utilities 207 | sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 208 | url: "https://pub.dev" 209 | source: hosted 210 | version: "0.2.0" 211 | meta: 212 | dependency: transitive 213 | description: 214 | name: meta 215 | sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" 216 | url: "https://pub.dev" 217 | source: hosted 218 | version: "1.9.1" 219 | octo_image: 220 | dependency: transitive 221 | description: 222 | name: octo_image 223 | sha256: "107f3ed1330006a3bea63615e81cf637433f5135a52466c7caa0e7152bca9143" 224 | url: "https://pub.dev" 225 | source: hosted 226 | version: "1.0.2" 227 | optimized_cached_image: 228 | dependency: "direct main" 229 | description: 230 | path: ".." 231 | relative: true 232 | source: path 233 | version: "3.0.1" 234 | path: 235 | dependency: transitive 236 | description: 237 | name: path 238 | sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" 239 | url: "https://pub.dev" 240 | source: hosted 241 | version: "1.8.3" 242 | path_provider: 243 | dependency: transitive 244 | description: 245 | name: path_provider 246 | sha256: "3087813781ab814e4157b172f1a11c46be20179fcc9bea043e0fba36bc0acaa2" 247 | url: "https://pub.dev" 248 | source: hosted 249 | version: "2.0.15" 250 | path_provider_android: 251 | dependency: transitive 252 | description: 253 | name: path_provider_android 254 | sha256: dfaa152e93c3a6fec632482928c770b2156dfb873582e99fbd6ac3b3de651d4c 255 | url: "https://pub.dev" 256 | source: hosted 257 | version: "2.0.14" 258 | path_provider_foundation: 259 | dependency: transitive 260 | description: 261 | name: path_provider_foundation 262 | sha256: "1995d88ec2948dac43edf8fe58eb434d35d22a2940ecee1a9fefcd62beee6eb3" 263 | url: "https://pub.dev" 264 | source: hosted 265 | version: "2.2.3" 266 | path_provider_linux: 267 | dependency: transitive 268 | description: 269 | name: path_provider_linux 270 | sha256: "367b9311fe9ce1421215bcc37dce9bde57b6640c7b790cee1974c2b0a691e074" 271 | url: "https://pub.dev" 272 | source: hosted 273 | version: "2.1.6" 274 | path_provider_platform_interface: 275 | dependency: transitive 276 | description: 277 | name: path_provider_platform_interface 278 | sha256: "27dc7a224fcd07444cb5e0e60423ccacea3e13cf00fc5282ac2c918132da931d" 279 | url: "https://pub.dev" 280 | source: hosted 281 | version: "2.0.4" 282 | path_provider_windows: 283 | dependency: transitive 284 | description: 285 | name: path_provider_windows 286 | sha256: "62dbb1bc45f1e7ba1094c9dd8ea46bdcffc254db7354b4988cb9326c9d2efcdd" 287 | url: "https://pub.dev" 288 | source: hosted 289 | version: "2.0.6" 290 | pedantic: 291 | dependency: transitive 292 | description: 293 | name: pedantic 294 | sha256: "67fc27ed9639506c856c840ccce7594d0bdcd91bc8d53d6e52359449a1d50602" 295 | url: "https://pub.dev" 296 | source: hosted 297 | version: "1.11.1" 298 | petitparser: 299 | dependency: transitive 300 | description: 301 | name: petitparser 302 | sha256: "2ebb289dc4764ec397f5cd3ca9881c6d17196130a7d646ed022a0dd9c2e25a71" 303 | url: "https://pub.dev" 304 | source: hosted 305 | version: "5.0.0" 306 | platform: 307 | dependency: transitive 308 | description: 309 | name: platform 310 | sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" 311 | url: "https://pub.dev" 312 | source: hosted 313 | version: "3.1.0" 314 | plugin_platform_interface: 315 | dependency: transitive 316 | description: 317 | name: plugin_platform_interface 318 | sha256: "075f927ebbab4262ace8d0b283929ac5410c0ac4e7fc123c76429564facfb757" 319 | url: "https://pub.dev" 320 | source: hosted 321 | version: "2.1.2" 322 | process: 323 | dependency: transitive 324 | description: 325 | name: process 326 | sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" 327 | url: "https://pub.dev" 328 | source: hosted 329 | version: "4.2.4" 330 | rxdart: 331 | dependency: transitive 332 | description: 333 | name: rxdart 334 | sha256: bc2d2b17b87fab32e2dca53ca3066d3147de6f96c74d76cfe1a379a24239c46d 335 | url: "https://pub.dev" 336 | source: hosted 337 | version: "0.27.3" 338 | sky_engine: 339 | dependency: transitive 340 | description: flutter 341 | source: sdk 342 | version: "0.0.99" 343 | source_span: 344 | dependency: transitive 345 | description: 346 | name: source_span 347 | sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 348 | url: "https://pub.dev" 349 | source: hosted 350 | version: "1.9.1" 351 | sprintf: 352 | dependency: transitive 353 | description: 354 | name: sprintf 355 | sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" 356 | url: "https://pub.dev" 357 | source: hosted 358 | version: "7.0.0" 359 | sqflite: 360 | dependency: transitive 361 | description: 362 | name: sqflite 363 | sha256: "51c09d414ca74b1cd4a5880d63763ebd2033a4fc6d921708c7c1e98c603735d4" 364 | url: "https://pub.dev" 365 | source: hosted 366 | version: "2.0.2+1" 367 | sqflite_common: 368 | dependency: transitive 369 | description: 370 | name: sqflite_common 371 | sha256: b504fc5b4576a05586a0bb99d9bcc0d37a78d9d5ed68b96c361d5d3a8e538275 372 | url: "https://pub.dev" 373 | source: hosted 374 | version: "2.2.1+1" 375 | stack_trace: 376 | dependency: transitive 377 | description: 378 | name: stack_trace 379 | sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 380 | url: "https://pub.dev" 381 | source: hosted 382 | version: "1.11.0" 383 | stream_channel: 384 | dependency: transitive 385 | description: 386 | name: stream_channel 387 | sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" 388 | url: "https://pub.dev" 389 | source: hosted 390 | version: "2.1.1" 391 | string_scanner: 392 | dependency: transitive 393 | description: 394 | name: string_scanner 395 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" 396 | url: "https://pub.dev" 397 | source: hosted 398 | version: "1.2.0" 399 | synchronized: 400 | dependency: transitive 401 | description: 402 | name: synchronized 403 | sha256: a7f0790927c0806ae0d5eb061c713748fa6070ef0037e391a2d53c3844c09dc2 404 | url: "https://pub.dev" 405 | source: hosted 406 | version: "3.0.0+2" 407 | term_glyph: 408 | dependency: transitive 409 | description: 410 | name: term_glyph 411 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 412 | url: "https://pub.dev" 413 | source: hosted 414 | version: "1.2.1" 415 | test_api: 416 | dependency: transitive 417 | description: 418 | name: test_api 419 | sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb 420 | url: "https://pub.dev" 421 | source: hosted 422 | version: "0.5.1" 423 | typed_data: 424 | dependency: transitive 425 | description: 426 | name: typed_data 427 | sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" 428 | url: "https://pub.dev" 429 | source: hosted 430 | version: "1.3.1" 431 | url_launcher: 432 | dependency: "direct main" 433 | description: 434 | name: url_launcher 435 | sha256: "16177b6719e7c904875fdae7366479fb96b304bce0ad043ac5e652269ac09e82" 436 | url: "https://pub.dev" 437 | source: hosted 438 | version: "6.1.2" 439 | url_launcher_android: 440 | dependency: transitive 441 | description: 442 | name: url_launcher_android 443 | sha256: "1ccd353c1bff66b49863527c02759f4d06b92744bd9777c96a00ca6a9e8e1d2f" 444 | url: "https://pub.dev" 445 | source: hosted 446 | version: "6.0.17" 447 | url_launcher_ios: 448 | dependency: transitive 449 | description: 450 | name: url_launcher_ios 451 | sha256: "6ba7dddee26c9fae27c9203c424631109d73c8fa26cfa7bc3e35e751cb87f62e" 452 | url: "https://pub.dev" 453 | source: hosted 454 | version: "6.0.17" 455 | url_launcher_linux: 456 | dependency: transitive 457 | description: 458 | name: url_launcher_linux 459 | sha256: "360fa359ab06bcb4f7c5cd3123a2a9a4d3364d4575d27c4b33468bd4497dd094" 460 | url: "https://pub.dev" 461 | source: hosted 462 | version: "3.0.1" 463 | url_launcher_macos: 464 | dependency: transitive 465 | description: 466 | name: url_launcher_macos 467 | sha256: a9b3ea9043eabfaadfa3fb89de67a11210d85569086d22b3854484beab8b3978 468 | url: "https://pub.dev" 469 | source: hosted 470 | version: "3.0.1" 471 | url_launcher_platform_interface: 472 | dependency: transitive 473 | description: 474 | name: url_launcher_platform_interface 475 | sha256: "1b9c4dab07794498b83b5f938e26b20f68c3b460a3015b0307f9541cb34ef93d" 476 | url: "https://pub.dev" 477 | source: hosted 478 | version: "2.0.5" 479 | url_launcher_web: 480 | dependency: transitive 481 | description: 482 | name: url_launcher_web 483 | sha256: "1c7d34f93353de7f7ad9cb239e8b1e680e759b73845d4970dedc4e0a926e9abe" 484 | url: "https://pub.dev" 485 | source: hosted 486 | version: "2.0.11" 487 | url_launcher_windows: 488 | dependency: transitive 489 | description: 490 | name: url_launcher_windows 491 | sha256: e3c3b16d3104260c10eea3b0e34272aaa57921f83148b0619f74c2eced9b7ef1 492 | url: "https://pub.dev" 493 | source: hosted 494 | version: "3.0.1" 495 | uuid: 496 | dependency: transitive 497 | description: 498 | name: uuid 499 | sha256: "2469694ad079893e3b434a627970c33f2fa5adc46dfe03c9617546969a9a8afc" 500 | url: "https://pub.dev" 501 | source: hosted 502 | version: "3.0.6" 503 | vector_math: 504 | dependency: transitive 505 | description: 506 | name: vector_math 507 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 508 | url: "https://pub.dev" 509 | source: hosted 510 | version: "2.1.4" 511 | win32: 512 | dependency: transitive 513 | description: 514 | name: win32 515 | sha256: c0e3a4f7be7dae51d8f152230b86627e3397c1ba8c3fa58e63d44a9f3edc9cef 516 | url: "https://pub.dev" 517 | source: hosted 518 | version: "2.6.1" 519 | xdg_directories: 520 | dependency: transitive 521 | description: 522 | name: xdg_directories 523 | sha256: "060b6e1c891d956f72b5ac9463466c37cce3fa962a921532fc001e86fe93438e" 524 | url: "https://pub.dev" 525 | source: hosted 526 | version: "0.2.0+1" 527 | xml: 528 | dependency: transitive 529 | description: 530 | name: xml 531 | sha256: ac0e3f4bf00ba2708c33fbabbbe766300e509f8c82dbd4ab6525039813f7e2fb 532 | url: "https://pub.dev" 533 | source: hosted 534 | version: "6.1.0" 535 | sdks: 536 | dart: ">=3.0.0 <4.0.0" 537 | flutter: ">=3.3.0" 538 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: optimized_cached_image_example 2 | description: Demonstrates how to use the optimized_cached_image plugin. 3 | publish_to: 'none' 4 | 5 | environment: 6 | sdk: '>=2.12.0 <3.0.0' 7 | 8 | dependencies: 9 | flutter: 10 | sdk: flutter 11 | 12 | # The following adds the Cupertino Icons font to your application. 13 | # Use with the CupertinoIcons class for iOS style icons. 14 | cupertino_icons: ^1.0.3 15 | url_launcher: ^6.0.9 16 | optimized_cached_image: 17 | path: ../ 18 | file: ^6.1.2 19 | 20 | dev_dependencies: 21 | flutter_test: 22 | sdk: flutter 23 | 24 | # For information on the generic Dart part of this file, see the 25 | # following page: https://dart.dev/tools/pub/pubspec 26 | 27 | # The following section is specific to Flutter. 28 | flutter: 29 | 30 | # The following line ensures that the Material Icons font is 31 | # included with your application, so that you can use the icons in 32 | # the material Icons class. 33 | uses-material-design: true 34 | 35 | # To add assets to your application, add an assets section, like this: 36 | # assets: 37 | # - images/a_dot_burr.jpeg 38 | # - images/a_dot_ham.jpeg 39 | 40 | # An image asset can refer to one or more resolution-specific "variants", see 41 | # https://flutter.dev/assets-and-images/#resolution-aware. 42 | 43 | # For details regarding adding assets from package dependencies, see 44 | # https://flutter.dev/assets-and-images/#from-packages 45 | 46 | # To add custom fonts to your application, add a fonts section here, 47 | # in this "flutter" section. Each entry in this list should have a 48 | # "family" key with the font family name, and a "fonts" key with a 49 | # list giving the asset and other descriptors for the font. For 50 | # example: 51 | # fonts: 52 | # - family: Schyler 53 | # fonts: 54 | # - asset: fonts/Schyler-Regular.ttf 55 | # - asset: fonts/Schyler-Italic.ttf 56 | # style: italic 57 | # - family: Trajan Pro 58 | # fonts: 59 | # - asset: fonts/TrajanPro.ttf 60 | # - asset: fonts/TrajanPro_Bold.ttf 61 | # weight: 700 62 | # 63 | # For details regarding fonts from package dependencies, 64 | # see https://flutter.dev/custom-fonts/#from-packages 65 | -------------------------------------------------------------------------------- /example/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/humblerookie/optimized_cached_image/c760d1d3a1b525cc1e71c93a808135d1f10d4d1e/example/web/favicon.png -------------------------------------------------------------------------------- /example/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/humblerookie/optimized_cached_image/c760d1d3a1b525cc1e71c93a808135d1f10d4d1e/example/web/icons/Icon-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/humblerookie/optimized_cached_image/c760d1d3a1b525cc1e71c93a808135d1f10d4d1e/example/web/icons/Icon-512.png -------------------------------------------------------------------------------- /example/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | example 18 | 19 | 20 | 21 | 24 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /example/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "short_name": "example", 4 | "start_url": ".", 5 | "display": "minimal-ui", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /example/windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /example/windows/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(example LANGUAGES CXX) 3 | 4 | set(BINARY_NAME "example") 5 | 6 | cmake_policy(SET CMP0063 NEW) 7 | 8 | set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") 9 | 10 | # Configure build options. 11 | get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 12 | if(IS_MULTICONFIG) 13 | set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" 14 | CACHE STRING "" FORCE) 15 | else() 16 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 17 | set(CMAKE_BUILD_TYPE "Debug" CACHE 18 | STRING "Flutter build mode" FORCE) 19 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 20 | "Debug" "Profile" "Release") 21 | endif() 22 | endif() 23 | 24 | set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") 25 | set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") 26 | set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") 27 | set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") 28 | 29 | # Use Unicode for all projects. 30 | add_definitions(-DUNICODE -D_UNICODE) 31 | 32 | # Compilation settings that should be applied to most targets. 33 | function(APPLY_STANDARD_SETTINGS TARGET) 34 | target_compile_features(${TARGET} PUBLIC cxx_std_17) 35 | target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") 36 | target_compile_options(${TARGET} PRIVATE /EHsc) 37 | target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") 38 | target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") 39 | endfunction() 40 | 41 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 42 | 43 | # Flutter library and tool build rules. 44 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 45 | 46 | # Application build 47 | add_subdirectory("runner") 48 | 49 | # Generated plugin build rules, which manage building the plugins and adding 50 | # them to the application. 51 | include(flutter/generated_plugins.cmake) 52 | 53 | 54 | # === Installation === 55 | # Support files are copied into place next to the executable, so that it can 56 | # run in place. This is done instead of making a separate bundle (as on Linux) 57 | # so that building and running from within Visual Studio will work. 58 | set(BUILD_BUNDLE_DIR "$") 59 | # Make the "install" step default, as it's required to run. 60 | set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) 61 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 62 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 63 | endif() 64 | 65 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 66 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") 67 | 68 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 69 | COMPONENT Runtime) 70 | 71 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 72 | COMPONENT Runtime) 73 | 74 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 75 | COMPONENT Runtime) 76 | 77 | if(PLUGIN_BUNDLED_LIBRARIES) 78 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" 79 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 80 | COMPONENT Runtime) 81 | endif() 82 | 83 | # Fully re-copy the assets directory on each build to avoid having stale files 84 | # from a previous install. 85 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 86 | install(CODE " 87 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 88 | " COMPONENT Runtime) 89 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 90 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 91 | 92 | # Install the AOT library on non-Debug builds only. 93 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 94 | CONFIGURATIONS Profile;Release 95 | COMPONENT Runtime) 96 | -------------------------------------------------------------------------------- /example/windows/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 4 | 5 | # Configuration provided via flutter tool. 6 | include(${EPHEMERAL_DIR}/generated_config.cmake) 7 | 8 | # TODO: Move the rest of this into files in ephemeral. See 9 | # https://github.com/flutter/flutter/issues/57146. 10 | set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") 11 | 12 | # === Flutter Library === 13 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") 14 | 15 | # Published to parent scope for install step. 16 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 17 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 18 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 19 | set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) 20 | 21 | list(APPEND FLUTTER_LIBRARY_HEADERS 22 | "flutter_export.h" 23 | "flutter_windows.h" 24 | "flutter_messenger.h" 25 | "flutter_plugin_registrar.h" 26 | ) 27 | list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") 28 | add_library(flutter INTERFACE) 29 | target_include_directories(flutter INTERFACE 30 | "${EPHEMERAL_DIR}" 31 | ) 32 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") 33 | add_dependencies(flutter flutter_assemble) 34 | 35 | # === Wrapper === 36 | list(APPEND CPP_WRAPPER_SOURCES_CORE 37 | "core_implementations.cc" 38 | "standard_codec.cc" 39 | ) 40 | list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") 41 | list(APPEND CPP_WRAPPER_SOURCES_PLUGIN 42 | "plugin_registrar.cc" 43 | ) 44 | list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") 45 | list(APPEND CPP_WRAPPER_SOURCES_APP 46 | "flutter_engine.cc" 47 | "flutter_view_controller.cc" 48 | ) 49 | list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") 50 | 51 | # Wrapper sources needed for a plugin. 52 | add_library(flutter_wrapper_plugin STATIC 53 | ${CPP_WRAPPER_SOURCES_CORE} 54 | ${CPP_WRAPPER_SOURCES_PLUGIN} 55 | ) 56 | apply_standard_settings(flutter_wrapper_plugin) 57 | set_target_properties(flutter_wrapper_plugin PROPERTIES 58 | POSITION_INDEPENDENT_CODE ON) 59 | set_target_properties(flutter_wrapper_plugin PROPERTIES 60 | CXX_VISIBILITY_PRESET hidden) 61 | target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) 62 | target_include_directories(flutter_wrapper_plugin PUBLIC 63 | "${WRAPPER_ROOT}/include" 64 | ) 65 | add_dependencies(flutter_wrapper_plugin flutter_assemble) 66 | 67 | # Wrapper sources needed for the runner. 68 | add_library(flutter_wrapper_app STATIC 69 | ${CPP_WRAPPER_SOURCES_CORE} 70 | ${CPP_WRAPPER_SOURCES_APP} 71 | ) 72 | apply_standard_settings(flutter_wrapper_app) 73 | target_link_libraries(flutter_wrapper_app PUBLIC flutter) 74 | target_include_directories(flutter_wrapper_app PUBLIC 75 | "${WRAPPER_ROOT}/include" 76 | ) 77 | add_dependencies(flutter_wrapper_app flutter_assemble) 78 | 79 | # === Flutter tool backend === 80 | # _phony_ is a non-existent file to force this command to run every time, 81 | # since currently there's no way to get a full input/output list from the 82 | # flutter tool. 83 | set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") 84 | set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) 85 | add_custom_command( 86 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 87 | ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} 88 | ${CPP_WRAPPER_SOURCES_APP} 89 | ${PHONY_OUTPUT} 90 | COMMAND ${CMAKE_COMMAND} -E env 91 | ${FLUTTER_TOOL_ENVIRONMENT} 92 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" 93 | windows-x64 $ 94 | ) 95 | add_custom_target(flutter_assemble DEPENDS 96 | "${FLUTTER_LIBRARY}" 97 | ${FLUTTER_LIBRARY_HEADERS} 98 | ${CPP_WRAPPER_SOURCES_CORE} 99 | ${CPP_WRAPPER_SOURCES_PLUGIN} 100 | ${CPP_WRAPPER_SOURCES_APP} 101 | ) 102 | -------------------------------------------------------------------------------- /example/windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(runner LANGUAGES CXX) 3 | 4 | add_executable(${BINARY_NAME} WIN32 5 | "flutter_window.cpp" 6 | "main.cpp" 7 | "run_loop.cpp" 8 | "utils.cpp" 9 | "win32_window.cpp" 10 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 11 | "Runner.rc" 12 | "runner.exe.manifest" 13 | ) 14 | apply_standard_settings(${BINARY_NAME}) 15 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 16 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 17 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 18 | add_dependencies(${BINARY_NAME} flutter_assemble) 19 | -------------------------------------------------------------------------------- /example/windows/runner/Runner.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #pragma code_page(65001) 4 | #include "resource.h" 5 | 6 | #define APSTUDIO_READONLY_SYMBOLS 7 | ///////////////////////////////////////////////////////////////////////////// 8 | // 9 | // Generated from the TEXTINCLUDE 2 resource. 10 | // 11 | #include "winres.h" 12 | 13 | ///////////////////////////////////////////////////////////////////////////// 14 | #undef APSTUDIO_READONLY_SYMBOLS 15 | 16 | ///////////////////////////////////////////////////////////////////////////// 17 | // English (United States) resources 18 | 19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 21 | 22 | #ifdef APSTUDIO_INVOKED 23 | ///////////////////////////////////////////////////////////////////////////// 24 | // 25 | // TEXTINCLUDE 26 | // 27 | 28 | 1 TEXTINCLUDE 29 | BEGIN 30 | "resource.h\0" 31 | END 32 | 33 | 2 TEXTINCLUDE 34 | BEGIN 35 | "#include ""winres.h""\r\n" 36 | "\0" 37 | END 38 | 39 | 3 TEXTINCLUDE 40 | BEGIN 41 | "\r\n" 42 | "\0" 43 | END 44 | 45 | #endif // APSTUDIO_INVOKED 46 | 47 | 48 | ///////////////////////////////////////////////////////////////////////////// 49 | // 50 | // Icon 51 | // 52 | 53 | // Icon with lowest ID value placed first to ensure application icon 54 | // remains consistent on all systems. 55 | IDI_APP_ICON ICON "resources\\app_icon.ico" 56 | 57 | 58 | ///////////////////////////////////////////////////////////////////////////// 59 | // 60 | // Version 61 | // 62 | 63 | #ifdef FLUTTER_BUILD_NUMBER 64 | #define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER 65 | #else 66 | #define VERSION_AS_NUMBER 1,0,0 67 | #endif 68 | 69 | #ifdef FLUTTER_BUILD_NAME 70 | #define VERSION_AS_STRING #FLUTTER_BUILD_NAME 71 | #else 72 | #define VERSION_AS_STRING "1.0.0" 73 | #endif 74 | 75 | VS_VERSION_INFO VERSIONINFO 76 | FILEVERSION VERSION_AS_NUMBER 77 | PRODUCTVERSION VERSION_AS_NUMBER 78 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK 79 | #ifdef _DEBUG 80 | FILEFLAGS VS_FF_DEBUG 81 | #else 82 | FILEFLAGS 0x0L 83 | #endif 84 | FILEOS VOS__WINDOWS32 85 | FILETYPE VFT_APP 86 | FILESUBTYPE 0x0L 87 | BEGIN 88 | BLOCK "StringFileInfo" 89 | BEGIN 90 | BLOCK "040904e4" 91 | BEGIN 92 | VALUE "CompanyName", "com.example" "\0" 93 | VALUE "FileDescription", "A new Flutter project." "\0" 94 | VALUE "FileVersion", VERSION_AS_STRING "\0" 95 | VALUE "InternalName", "example" "\0" 96 | VALUE "LegalCopyright", "Copyright (C) 2020 com.example. All rights reserved." "\0" 97 | VALUE "OriginalFilename", "example.exe" "\0" 98 | VALUE "ProductName", "example" "\0" 99 | VALUE "ProductVersion", VERSION_AS_STRING "\0" 100 | END 101 | END 102 | BLOCK "VarFileInfo" 103 | BEGIN 104 | VALUE "Translation", 0x409, 1252 105 | END 106 | END 107 | 108 | #endif // English (United States) resources 109 | ///////////////////////////////////////////////////////////////////////////// 110 | 111 | 112 | 113 | #ifndef APSTUDIO_INVOKED 114 | ///////////////////////////////////////////////////////////////////////////// 115 | // 116 | // Generated from the TEXTINCLUDE 3 resource. 117 | // 118 | 119 | 120 | ///////////////////////////////////////////////////////////////////////////// 121 | #endif // not APSTUDIO_INVOKED 122 | -------------------------------------------------------------------------------- /example/windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(RunLoop* run_loop, 8 | const flutter::DartProject& project) 9 | : run_loop_(run_loop), project_(project) {} 10 | 11 | FlutterWindow::~FlutterWindow() {} 12 | 13 | bool FlutterWindow::OnCreate() { 14 | if (!Win32Window::OnCreate()) { 15 | return false; 16 | } 17 | 18 | RECT frame = GetClientArea(); 19 | 20 | // The size here must match the window dimensions to avoid unnecessary surface 21 | // creation / destruction in the startup path. 22 | flutter_controller_ = std::make_unique( 23 | frame.right - frame.left, frame.bottom - frame.top, project_); 24 | // Ensure that basic setup of the controller was successful. 25 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 26 | return false; 27 | } 28 | RegisterPlugins(flutter_controller_->engine()); 29 | run_loop_->RegisterFlutterInstance(flutter_controller_->engine()); 30 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 31 | return true; 32 | } 33 | 34 | void FlutterWindow::OnDestroy() { 35 | if (flutter_controller_) { 36 | run_loop_->UnregisterFlutterInstance(flutter_controller_->engine()); 37 | flutter_controller_ = nullptr; 38 | } 39 | 40 | Win32Window::OnDestroy(); 41 | } 42 | 43 | LRESULT 44 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 45 | WPARAM const wparam, 46 | LPARAM const lparam) noexcept { 47 | // Give Flutter, including plugins, an opporutunity to handle window messages. 48 | if (flutter_controller_) { 49 | std::optional result = 50 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 51 | lparam); 52 | if (result) { 53 | return *result; 54 | } 55 | } 56 | 57 | switch (message) { 58 | case WM_FONTCHANGE: 59 | flutter_controller_->engine()->ReloadSystemFonts(); 60 | break; 61 | } 62 | 63 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 64 | } 65 | -------------------------------------------------------------------------------- /example/windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "run_loop.h" 10 | #include "win32_window.h" 11 | 12 | // A window that does nothing but host a Flutter view. 13 | class FlutterWindow : public Win32Window { 14 | public: 15 | // Creates a new FlutterWindow driven by the |run_loop|, hosting a 16 | // Flutter view running |project|. 17 | explicit FlutterWindow(RunLoop* run_loop, 18 | const flutter::DartProject& project); 19 | virtual ~FlutterWindow(); 20 | 21 | protected: 22 | // Win32Window: 23 | bool OnCreate() override; 24 | void OnDestroy() override; 25 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 26 | LPARAM const lparam) noexcept override; 27 | 28 | private: 29 | // The run loop driving events for this window. 30 | RunLoop* run_loop_; 31 | 32 | // The project to run. 33 | flutter::DartProject project_; 34 | 35 | // The Flutter instance hosted by this window. 36 | std::unique_ptr flutter_controller_; 37 | }; 38 | 39 | #endif // RUNNER_FLUTTER_WINDOW_H_ 40 | -------------------------------------------------------------------------------- /example/windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "run_loop.h" 7 | #include "utils.h" 8 | 9 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 10 | _In_ wchar_t *command_line, _In_ int show_command) { 11 | // Attach to console when present (e.g., 'flutter run') or create a 12 | // new console when running with a debugger. 13 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 14 | CreateAndAttachConsole(); 15 | } 16 | 17 | // Initialize COM, so that it is available for use in the library and/or 18 | // plugins. 19 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 20 | 21 | RunLoop run_loop; 22 | 23 | flutter::DartProject project(L"data"); 24 | FlutterWindow window(&run_loop, project); 25 | Win32Window::Point origin(10, 10); 26 | Win32Window::Size size(1280, 720); 27 | if (!window.CreateAndShow(L"example", origin, size)) { 28 | return EXIT_FAILURE; 29 | } 30 | window.SetQuitOnClose(true); 31 | 32 | run_loop.Run(); 33 | 34 | ::CoUninitialize(); 35 | return EXIT_SUCCESS; 36 | } 37 | -------------------------------------------------------------------------------- /example/windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /example/windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/humblerookie/optimized_cached_image/c760d1d3a1b525cc1e71c93a808135d1f10d4d1e/example/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /example/windows/runner/run_loop.cpp: -------------------------------------------------------------------------------- 1 | #include "run_loop.h" 2 | 3 | #include 4 | 5 | #include 6 | 7 | RunLoop::RunLoop() {} 8 | 9 | RunLoop::~RunLoop() {} 10 | 11 | void RunLoop::Run() { 12 | bool keep_running = true; 13 | TimePoint next_flutter_event_time = TimePoint::clock::now(); 14 | while (keep_running) { 15 | std::chrono::nanoseconds wait_duration = 16 | std::max(std::chrono::nanoseconds(0), 17 | next_flutter_event_time - TimePoint::clock::now()); 18 | ::MsgWaitForMultipleObjects( 19 | 0, nullptr, FALSE, static_cast(wait_duration.count() / 1000), 20 | QS_ALLINPUT); 21 | bool processed_events = false; 22 | MSG message; 23 | // All pending Windows messages must be processed; MsgWaitForMultipleObjects 24 | // won't return again for items left in the queue after PeekMessage. 25 | while (::PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) { 26 | processed_events = true; 27 | if (message.message == WM_QUIT) { 28 | keep_running = false; 29 | break; 30 | } 31 | ::TranslateMessage(&message); 32 | ::DispatchMessage(&message); 33 | // Allow Flutter to process messages each time a Windows message is 34 | // processed, to prevent starvation. 35 | next_flutter_event_time = 36 | std::min(next_flutter_event_time, ProcessFlutterMessages()); 37 | } 38 | // If the PeekMessage loop didn't run, process Flutter messages. 39 | if (!processed_events) { 40 | next_flutter_event_time = 41 | std::min(next_flutter_event_time, ProcessFlutterMessages()); 42 | } 43 | } 44 | } 45 | 46 | void RunLoop::RegisterFlutterInstance( 47 | flutter::FlutterEngine* flutter_instance) { 48 | flutter_instances_.insert(flutter_instance); 49 | } 50 | 51 | void RunLoop::UnregisterFlutterInstance( 52 | flutter::FlutterEngine* flutter_instance) { 53 | flutter_instances_.erase(flutter_instance); 54 | } 55 | 56 | RunLoop::TimePoint RunLoop::ProcessFlutterMessages() { 57 | TimePoint next_event_time = TimePoint::max(); 58 | for (auto instance : flutter_instances_) { 59 | std::chrono::nanoseconds wait_duration = instance->ProcessMessages(); 60 | if (wait_duration != std::chrono::nanoseconds::max()) { 61 | next_event_time = 62 | std::min(next_event_time, TimePoint::clock::now() + wait_duration); 63 | } 64 | } 65 | return next_event_time; 66 | } 67 | -------------------------------------------------------------------------------- /example/windows/runner/run_loop.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_RUN_LOOP_H_ 2 | #define RUNNER_RUN_LOOP_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | // A runloop that will service events for Flutter instances as well 10 | // as native messages. 11 | class RunLoop { 12 | public: 13 | RunLoop(); 14 | ~RunLoop(); 15 | 16 | // Prevent copying 17 | RunLoop(RunLoop const&) = delete; 18 | RunLoop& operator=(RunLoop const&) = delete; 19 | 20 | // Runs the run loop until the application quits. 21 | void Run(); 22 | 23 | // Registers the given Flutter instance for event servicing. 24 | void RegisterFlutterInstance( 25 | flutter::FlutterEngine* flutter_instance); 26 | 27 | // Unregisters the given Flutter instance from event servicing. 28 | void UnregisterFlutterInstance( 29 | flutter::FlutterEngine* flutter_instance); 30 | 31 | private: 32 | using TimePoint = std::chrono::steady_clock::time_point; 33 | 34 | // Processes all currently pending messages for registered Flutter instances. 35 | TimePoint ProcessFlutterMessages(); 36 | 37 | std::set flutter_instances_; 38 | }; 39 | 40 | #endif // RUNNER_RUN_LOOP_H_ 41 | -------------------------------------------------------------------------------- /example/windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /example/windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /example/windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | // Creates a console for the process, and redirects stdout and stderr to 5 | // it for both the runner and the Flutter library. 6 | void CreateAndAttachConsole(); 7 | 8 | #endif // RUNNER_UTILS_H_ 9 | -------------------------------------------------------------------------------- /example/windows/runner/win32_window.cpp: -------------------------------------------------------------------------------- 1 | #include "win32_window.h" 2 | 3 | #include 4 | 5 | #include "resource.h" 6 | 7 | namespace { 8 | 9 | constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; 10 | 11 | // The number of Win32Window objects that currently exist. 12 | static int g_active_window_count = 0; 13 | 14 | using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); 15 | 16 | // Scale helper to convert logical scaler values to physical using passed in 17 | // scale factor 18 | int Scale(int source, double scale_factor) { 19 | return static_cast(source * scale_factor); 20 | } 21 | 22 | // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. 23 | // This API is only needed for PerMonitor V1 awareness mode. 24 | void EnableFullDpiSupportIfAvailable(HWND hwnd) { 25 | HMODULE user32_module = LoadLibraryA("User32.dll"); 26 | if (!user32_module) { 27 | return; 28 | } 29 | auto enable_non_client_dpi_scaling = 30 | reinterpret_cast( 31 | GetProcAddress(user32_module, "EnableNonClientDpiScaling")); 32 | if (enable_non_client_dpi_scaling != nullptr) { 33 | enable_non_client_dpi_scaling(hwnd); 34 | FreeLibrary(user32_module); 35 | } 36 | } 37 | 38 | } // namespace 39 | 40 | // Manages the Win32Window's window class registration. 41 | class WindowClassRegistrar { 42 | public: 43 | ~WindowClassRegistrar() = default; 44 | 45 | // Returns the singleton registar instance. 46 | static WindowClassRegistrar* GetInstance() { 47 | if (!instance_) { 48 | instance_ = new WindowClassRegistrar(); 49 | } 50 | return instance_; 51 | } 52 | 53 | // Returns the name of the window class, registering the class if it hasn't 54 | // previously been registered. 55 | const wchar_t* GetWindowClass(); 56 | 57 | // Unregisters the window class. Should only be called if there are no 58 | // instances of the window. 59 | void UnregisterWindowClass(); 60 | 61 | private: 62 | WindowClassRegistrar() = default; 63 | 64 | static WindowClassRegistrar* instance_; 65 | 66 | bool class_registered_ = false; 67 | }; 68 | 69 | WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; 70 | 71 | const wchar_t* WindowClassRegistrar::GetWindowClass() { 72 | if (!class_registered_) { 73 | WNDCLASS window_class{}; 74 | window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); 75 | window_class.lpszClassName = kWindowClassName; 76 | window_class.style = CS_HREDRAW | CS_VREDRAW; 77 | window_class.cbClsExtra = 0; 78 | window_class.cbWndExtra = 0; 79 | window_class.hInstance = GetModuleHandle(nullptr); 80 | window_class.hIcon = 81 | LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); 82 | window_class.hbrBackground = 0; 83 | window_class.lpszMenuName = nullptr; 84 | window_class.lpfnWndProc = Win32Window::WndProc; 85 | RegisterClass(&window_class); 86 | class_registered_ = true; 87 | } 88 | return kWindowClassName; 89 | } 90 | 91 | void WindowClassRegistrar::UnregisterWindowClass() { 92 | UnregisterClass(kWindowClassName, nullptr); 93 | class_registered_ = false; 94 | } 95 | 96 | Win32Window::Win32Window() { 97 | ++g_active_window_count; 98 | } 99 | 100 | Win32Window::~Win32Window() { 101 | --g_active_window_count; 102 | Destroy(); 103 | } 104 | 105 | bool Win32Window::CreateAndShow(const std::wstring& title, 106 | const Point& origin, 107 | const Size& size) { 108 | Destroy(); 109 | 110 | const wchar_t* window_class = 111 | WindowClassRegistrar::GetInstance()->GetWindowClass(); 112 | 113 | const POINT target_point = {static_cast(origin.x), 114 | static_cast(origin.y)}; 115 | HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); 116 | UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); 117 | double scale_factor = dpi / 96.0; 118 | 119 | HWND window = CreateWindow( 120 | window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, 121 | Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), 122 | Scale(size.width, scale_factor), Scale(size.height, scale_factor), 123 | nullptr, nullptr, GetModuleHandle(nullptr), this); 124 | 125 | if (!window) { 126 | return false; 127 | } 128 | 129 | return OnCreate(); 130 | } 131 | 132 | // static 133 | LRESULT CALLBACK Win32Window::WndProc(HWND const window, 134 | UINT const message, 135 | WPARAM const wparam, 136 | LPARAM const lparam) noexcept { 137 | if (message == WM_NCCREATE) { 138 | auto window_struct = reinterpret_cast(lparam); 139 | SetWindowLongPtr(window, GWLP_USERDATA, 140 | reinterpret_cast(window_struct->lpCreateParams)); 141 | 142 | auto that = static_cast(window_struct->lpCreateParams); 143 | EnableFullDpiSupportIfAvailable(window); 144 | that->window_handle_ = window; 145 | } else if (Win32Window* that = GetThisFromHandle(window)) { 146 | return that->MessageHandler(window, message, wparam, lparam); 147 | } 148 | 149 | return DefWindowProc(window, message, wparam, lparam); 150 | } 151 | 152 | LRESULT 153 | Win32Window::MessageHandler(HWND hwnd, 154 | UINT const message, 155 | WPARAM const wparam, 156 | LPARAM const lparam) noexcept { 157 | switch (message) { 158 | case WM_DESTROY: 159 | window_handle_ = nullptr; 160 | Destroy(); 161 | if (quit_on_close_) { 162 | PostQuitMessage(0); 163 | } 164 | return 0; 165 | 166 | case WM_DPICHANGED: { 167 | auto newRectSize = reinterpret_cast(lparam); 168 | LONG newWidth = newRectSize->right - newRectSize->left; 169 | LONG newHeight = newRectSize->bottom - newRectSize->top; 170 | 171 | SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, 172 | newHeight, SWP_NOZORDER | SWP_NOACTIVATE); 173 | 174 | return 0; 175 | } 176 | case WM_SIZE: 177 | RECT rect = GetClientArea(); 178 | if (child_content_ != nullptr) { 179 | // Size and position the child window. 180 | MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, 181 | rect.bottom - rect.top, TRUE); 182 | } 183 | return 0; 184 | 185 | case WM_ACTIVATE: 186 | if (child_content_ != nullptr) { 187 | SetFocus(child_content_); 188 | } 189 | return 0; 190 | } 191 | 192 | return DefWindowProc(window_handle_, message, wparam, lparam); 193 | } 194 | 195 | void Win32Window::Destroy() { 196 | OnDestroy(); 197 | 198 | if (window_handle_) { 199 | DestroyWindow(window_handle_); 200 | window_handle_ = nullptr; 201 | } 202 | if (g_active_window_count == 0) { 203 | WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); 204 | } 205 | } 206 | 207 | Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { 208 | return reinterpret_cast( 209 | GetWindowLongPtr(window, GWLP_USERDATA)); 210 | } 211 | 212 | void Win32Window::SetChildContent(HWND content) { 213 | child_content_ = content; 214 | SetParent(content, window_handle_); 215 | RECT frame = GetClientArea(); 216 | 217 | MoveWindow(content, frame.left, frame.top, frame.right - frame.left, 218 | frame.bottom - frame.top, true); 219 | 220 | SetFocus(child_content_); 221 | } 222 | 223 | RECT Win32Window::GetClientArea() { 224 | RECT frame; 225 | GetClientRect(window_handle_, &frame); 226 | return frame; 227 | } 228 | 229 | HWND Win32Window::GetHandle() { 230 | return window_handle_; 231 | } 232 | 233 | void Win32Window::SetQuitOnClose(bool quit_on_close) { 234 | quit_on_close_ = quit_on_close; 235 | } 236 | 237 | bool Win32Window::OnCreate() { 238 | // No-op; provided for subclasses. 239 | return true; 240 | } 241 | 242 | void Win32Window::OnDestroy() { 243 | // No-op; provided for subclasses. 244 | } 245 | -------------------------------------------------------------------------------- /example/windows/runner/win32_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_WIN32_WINDOW_H_ 2 | #define RUNNER_WIN32_WINDOW_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be 11 | // inherited from by classes that wish to specialize with custom 12 | // rendering and input handling 13 | class Win32Window { 14 | public: 15 | struct Point { 16 | unsigned int x; 17 | unsigned int y; 18 | Point(unsigned int x, unsigned int y) : x(x), y(y) {} 19 | }; 20 | 21 | struct Size { 22 | unsigned int width; 23 | unsigned int height; 24 | Size(unsigned int width, unsigned int height) 25 | : width(width), height(height) {} 26 | }; 27 | 28 | Win32Window(); 29 | virtual ~Win32Window(); 30 | 31 | // Creates and shows a win32 window with |title| and position and size using 32 | // |origin| and |size|. New windows are created on the default monitor. Window 33 | // sizes are specified to the OS in physical pixels, hence to ensure a 34 | // consistent size to will treat the width height passed in to this function 35 | // as logical pixels and scale to appropriate for the default monitor. Returns 36 | // true if the window was created successfully. 37 | bool CreateAndShow(const std::wstring& title, 38 | const Point& origin, 39 | const Size& size); 40 | 41 | // Release OS resources associated with window. 42 | void Destroy(); 43 | 44 | // Inserts |content| into the window tree. 45 | void SetChildContent(HWND content); 46 | 47 | // Returns the backing Window handle to enable clients to set icon and other 48 | // window properties. Returns nullptr if the window has been destroyed. 49 | HWND GetHandle(); 50 | 51 | // If true, closing this window will quit the application. 52 | void SetQuitOnClose(bool quit_on_close); 53 | 54 | // Return a RECT representing the bounds of the current client area. 55 | RECT GetClientArea(); 56 | 57 | protected: 58 | // Processes and route salient window messages for mouse handling, 59 | // size change and DPI. Delegates handling of these to member overloads that 60 | // inheriting classes can handle. 61 | virtual LRESULT MessageHandler(HWND window, 62 | UINT const message, 63 | WPARAM const wparam, 64 | LPARAM const lparam) noexcept; 65 | 66 | // Called when CreateAndShow is called, allowing subclass window-related 67 | // setup. Subclasses should return false if setup fails. 68 | virtual bool OnCreate(); 69 | 70 | // Called when Destroy is called. 71 | virtual void OnDestroy(); 72 | 73 | private: 74 | friend class WindowClassRegistrar; 75 | 76 | // OS callback called by message pump. Handles the WM_NCCREATE message which 77 | // is passed when the non-client area is being created and enables automatic 78 | // non-client DPI scaling so that the non-client area automatically 79 | // responsponds to changes in DPI. All other messages are handled by 80 | // MessageHandler. 81 | static LRESULT CALLBACK WndProc(HWND const window, 82 | UINT const message, 83 | WPARAM const wparam, 84 | LPARAM const lparam) noexcept; 85 | 86 | // Retrieves a class instance pointer for |window| 87 | static Win32Window* GetThisFromHandle(HWND const window) noexcept; 88 | 89 | bool quit_on_close_ = false; 90 | 91 | // window handle for top level window. 92 | HWND window_handle_ = nullptr; 93 | 94 | // window handle for hosted content. 95 | HWND child_content_ = nullptr; 96 | }; 97 | 98 | #endif // RUNNER_WIN32_WINDOW_H_ 99 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/Generated.xcconfig 37 | /Flutter/flutter_export_environment.sh -------------------------------------------------------------------------------- /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/humblerookie/optimized_cached_image/c760d1d3a1b525cc1e71c93a808135d1f10d4d1e/ios/Assets/.gitkeep -------------------------------------------------------------------------------- /ios/optimized_cached_image.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. 3 | # Run `pod lib lint optimized_cached_image.podspec' to validate before publishing. 4 | # 5 | Pod::Spec.new do |s| 6 | s.name = 'optimized_cached_image' 7 | s.version = '0.0.1' 8 | s.summary = 'A library for loading images from network, resizing and caching them for memory sensitivity' 9 | s.description = <<-DESC 10 | A library for loading images from network, resizing and caching them for memory sensitivity 11 | DESC 12 | s.homepage = 'http://example.com' 13 | s.license = { :file => '../LICENSE' } 14 | s.author = { 'Your Company' => 'email@example.com' } 15 | s.source = { :path => '.' } 16 | s.source_files = 'Classes/**/*' 17 | s.dependency 'Flutter' 18 | s.platform = :ios, '8.0' 19 | 20 | # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported. 21 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } 22 | s.swift_version = '5.0' 23 | end 24 | -------------------------------------------------------------------------------- /lib/optimized_cached_image.dart: -------------------------------------------------------------------------------- 1 | /// A flutter library for loading images from network, resizing and caching them 2 | /// for memory sensitivity. This resizes and stores the images in cache based on 3 | /// parent container constraints and hence loads images of lower size into 4 | /// memory. This is heavily inspired by cached_network_image library. 5 | library optimized_cached_image; 6 | 7 | export 'package:flutter_cache_manager/src/result/download_progress.dart'; 8 | export 'src/oci_widget.dart'; 9 | export 'src/image_provider/optimized_cached_image_provider.dart'; 10 | export 'src/image_provider/multi_image_stream_completer.dart'; 11 | export 'src/debug_tools.dart'; 12 | -------------------------------------------------------------------------------- /lib/src/cache/default_image_cache_manager.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_cache_manager/flutter_cache_manager.dart'; 2 | import 'package:optimized_cached_image/src/cache/image_cache_manager.dart'; 3 | 4 | /// The DefaultCacheManager that can be easily used directly. The code of 5 | /// this implementation can be used as inspiration for more complex cache 6 | /// managers. 7 | class DefaultImageCacheManager extends CacheManager with OicImageCacheManager { 8 | static const key = 'libCachedImageData'; 9 | 10 | static DefaultImageCacheManager? _instance; 11 | factory DefaultImageCacheManager() { 12 | _instance ??= DefaultImageCacheManager._(); 13 | return _instance!; 14 | } 15 | DefaultImageCacheManager._() : super(Config(key)); 16 | } 17 | -------------------------------------------------------------------------------- /lib/src/cache/image_cache_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:math'; 3 | import 'dart:ui' as ui; 4 | 5 | import 'package:file/file.dart'; 6 | import 'package:flutter/widgets.dart'; 7 | import 'package:flutter_cache_manager/flutter_cache_manager.dart'; 8 | 9 | import '../transformer/image_transformer.dart'; 10 | 11 | const supportedFileNames = ['jpg', 'jpeg', 'png', 'tga', 'gif', 'cur', 'ico']; 12 | mixin OicImageCacheManager on BaseCacheManager { 13 | /// Returns a resized image file to fit within maxHeight and maxWidth. It 14 | /// tries to keep the aspect ratio. It stores the resized image by adding 15 | /// the size to the key or url. For example when resizing 16 | /// https://via.placeholder.com/150 to max width 100 and height 75 it will 17 | /// store it with cacheKey resized_w100_h75_https://via.placeholder.com/150. 18 | /// 19 | /// When the resized file is not found in the cache the original is fetched 20 | /// from the cache or online and stored in the cache. Then it is resized 21 | /// and returned to the caller. 22 | /// 23 | final ImageTransformer transformer = DefaultImageTransformer(); 24 | 25 | Stream getImageFile( 26 | String url, { 27 | String? key, 28 | Map? headers, 29 | bool withProgress = false, 30 | int? maxHeight, 31 | int? maxWidth, 32 | }) async* { 33 | if (maxHeight == null && maxWidth == null) { 34 | yield* getFileStream(url, 35 | key: key, headers: headers, withProgress: withProgress); 36 | return; 37 | } 38 | key ??= url; 39 | var resizedKey = 'resized'; 40 | if (maxWidth != null) resizedKey += '_w$maxWidth'; 41 | if (maxHeight != null) resizedKey += '_h$maxHeight'; 42 | resizedKey += '_$key'; 43 | 44 | var fromCache = await getFileFromCache(resizedKey); 45 | if (fromCache != null) { 46 | yield fromCache; 47 | if (fromCache.validTill.isAfter(DateTime.now())) { 48 | return; 49 | } 50 | withProgress = false; 51 | } 52 | if (!_runningResizes.containsKey(resizedKey)) { 53 | _runningResizes[resizedKey] = _fetchedResizedFile( 54 | url, 55 | key, 56 | resizedKey, 57 | headers, 58 | withProgress, 59 | maxWidth: maxWidth, 60 | maxHeight: maxHeight, 61 | ); 62 | } 63 | yield* _runningResizes[resizedKey]!; 64 | _runningResizes.remove(resizedKey); 65 | } 66 | 67 | final Map> _runningResizes = {}; 68 | Future _resizeImageFile( 69 | FileInfo originalFile, 70 | String key, 71 | int? maxWidth, 72 | int? maxHeight, 73 | ) async { 74 | var originalFileName = originalFile.file.path; 75 | var fileExtension = originalFileName.split('.').last; 76 | if (!supportedFileNames.contains(fileExtension)) { 77 | return originalFile; 78 | } 79 | 80 | var image = await _decodeImage(originalFile.file); 81 | var shouldResize = maxWidth != null 82 | ? image.width > maxWidth 83 | : false || maxHeight != null 84 | ? image.height > maxHeight 85 | : false; 86 | 87 | if (!shouldResize) return originalFile; 88 | 89 | if (maxWidth != null && maxHeight != null) { 90 | var resizeFactorWidth = image.width / maxWidth; 91 | var resizeFactorHeight = image.height / maxHeight; 92 | var resizeFactor = max(resizeFactorHeight, resizeFactorWidth); 93 | 94 | maxWidth = (image.width / resizeFactor).round(); 95 | maxHeight = (image.height / resizeFactor).round(); 96 | } 97 | var resizedFile = 98 | await transformer.transform(originalFile, maxWidth, maxHeight); 99 | var maxAge = originalFile.validTill.difference(DateTime.now()); 100 | 101 | var resizedFileContent = resizedFile.file.openRead(); 102 | var file = await putFileStream( 103 | originalFile.originalUrl, 104 | resizedFileContent, 105 | key: key, 106 | maxAge: maxAge, 107 | fileExtension: fileExtension, 108 | ); 109 | 110 | return FileInfo( 111 | file, 112 | originalFile.source, 113 | originalFile.validTill, 114 | originalFile.originalUrl, 115 | ); 116 | } 117 | 118 | Stream _fetchedResizedFile( 119 | String url, 120 | String originalKey, 121 | String resizedKey, 122 | Map? headers, 123 | bool withProgress, { 124 | int? maxWidth, 125 | int? maxHeight, 126 | }) async* { 127 | await for (var response in getFileStream( 128 | url, 129 | key: originalKey, 130 | headers: headers, 131 | withProgress: withProgress, 132 | )) { 133 | if (response is DownloadProgress) { 134 | yield response; 135 | } 136 | if (response is FileInfo) { 137 | yield await _resizeImageFile( 138 | response, 139 | resizedKey, 140 | maxWidth, 141 | maxHeight, 142 | ); 143 | } 144 | } 145 | } 146 | } 147 | 148 | Future _decodeImage(File file, 149 | {int? width, int? height, bool allowUpscaling = false}) { 150 | var shouldResize = width != null || height != null; 151 | var fileImage = FileImage(file); 152 | final image = shouldResize 153 | ? ResizeImage(fileImage, 154 | width: width, height: height, allowUpscaling: allowUpscaling) 155 | : fileImage as ImageProvider; 156 | final completer = Completer(); 157 | image 158 | .resolve(const ImageConfiguration()) 159 | .addListener(ImageStreamListener((info, _) { 160 | completer.complete(info.image); 161 | image.evict(); 162 | })); 163 | return completer.future; 164 | } 165 | -------------------------------------------------------------------------------- /lib/src/debug_tools.dart: -------------------------------------------------------------------------------- 1 | import 'dart:developer' as developer; 2 | 3 | abstract class Logger { 4 | static bool enableLogging = false; 5 | static const TAG = "optimized_image_cache"; 6 | } 7 | 8 | void log(String message) { 9 | if (Logger.enableLogging) { 10 | developer.log(message, name: Logger.TAG); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/src/image_provider/_image_provider_io.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async' show Future, StreamController, scheduleMicrotask; 2 | import 'dart:ui' as ui show Codec; 3 | import 'dart:ui'; 4 | 5 | import 'package:flutter/foundation.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter_cache_manager/flutter_cache_manager.dart'; 8 | import 'package:optimized_cached_image/src/cache/default_image_cache_manager.dart'; 9 | import 'package:optimized_cached_image/src/cache/image_cache_manager.dart'; 10 | 11 | import '../../optimized_cached_image.dart' show ImageRenderMethodForWeb; 12 | import 'multi_image_stream_completer.dart'; 13 | import 'optimized_cached_image_provider.dart' as image_provider; 14 | 15 | /// IO implementation of the CachedNetworkImageProvider; the ImageProvider to 16 | /// load network images using a cache. 17 | class OptimizedCacheImageProvider 18 | extends ImageProvider 19 | implements image_provider.OptimizedCacheImageProvider { 20 | /// Creates an ImageProvider which loads an image from the [url], using the [scale]. 21 | /// When the image fails to load [errorListener] is called. 22 | const OptimizedCacheImageProvider( 23 | this.url, { 24 | this.maxHeight, 25 | this.maxWidth, 26 | this.scale = 1.0, 27 | this.errorListener, 28 | this.headers, 29 | this.cacheManager, 30 | this.cacheKey, 31 | //ignore: avoid_unused_constructor_parameters 32 | ImageRenderMethodForWeb? imageRenderMethodForWeb, 33 | }); 34 | 35 | @override 36 | final BaseCacheManager? cacheManager; 37 | 38 | /// Web url of the image to load 39 | @override 40 | final String url; 41 | 42 | /// Cache key of the image to cache 43 | @override 44 | final String? cacheKey; 45 | 46 | /// Scale of the image 47 | @override 48 | final double scale; 49 | 50 | /// Listener to be called when images fails to load. 51 | @override 52 | final image_provider.ErrorListener? errorListener; 53 | 54 | /// Set headers for the image provider, for example for authentication 55 | @override 56 | final Map? headers; 57 | 58 | @override 59 | final int? maxHeight; 60 | 61 | @override 62 | final int? maxWidth; 63 | 64 | @override 65 | Future obtainKey( 66 | ImageConfiguration configuration) { 67 | return SynchronousFuture(this); 68 | } 69 | 70 | @override 71 | ImageStreamCompleter loadImage( 72 | image_provider.OptimizedCacheImageProvider key, 73 | ImageDecoderCallback decode, 74 | ) { 75 | final chunkEvents = StreamController(); 76 | return MultiImageStreamCompleter( 77 | codec: _loadAsync(key, chunkEvents, decode), 78 | chunkEvents: chunkEvents.stream, 79 | scale: key.scale, 80 | informationCollector: () sync* { 81 | yield DiagnosticsProperty( 82 | 'Image provider: $this \n Image key: $key', 83 | this, 84 | style: DiagnosticsTreeStyle.errorProperty, 85 | ); 86 | }, 87 | ); 88 | } 89 | 90 | Stream _loadAsync( 91 | image_provider.OptimizedCacheImageProvider key, 92 | StreamController chunkEvents, 93 | ImageDecoderCallback decode, 94 | ) async* { 95 | assert(key == this); 96 | try { 97 | var mngr = cacheManager ?? DefaultImageCacheManager(); 98 | assert( 99 | mngr is OicImageCacheManager || 100 | (maxWidth == null && maxHeight == null), 101 | 'To resize the image with a CacheManager the ' 102 | 'CacheManager needs to be an ImageCacheManager. maxWidth and ' 103 | 'maxHeight will be ignored when a normal CacheManager is used.'); 104 | 105 | var stream = mngr is OicImageCacheManager 106 | ? mngr.getImageFile(key.url, 107 | maxHeight: maxHeight, 108 | maxWidth: maxWidth, 109 | withProgress: true, 110 | headers: headers, 111 | key: key.cacheKey) 112 | : mngr.getFileStream(key.url, 113 | withProgress: true, headers: headers, key: key.cacheKey); 114 | 115 | await for (var result in stream) { 116 | if (result is DownloadProgress) { 117 | chunkEvents.add(ImageChunkEvent( 118 | cumulativeBytesLoaded: result.downloaded, 119 | expectedTotalBytes: result.totalSize, 120 | )); 121 | } 122 | if (result is FileInfo) { 123 | var file = result.file; 124 | var bytes = 125 | await ImmutableBuffer.fromUint8List(await file.readAsBytes()); 126 | var decoded = await decode(bytes); 127 | yield decoded; 128 | } 129 | } 130 | } catch (e) { 131 | // Depending on where the exception was thrown, the image cache may not 132 | // have had a chance to track the key in the cache at all. 133 | // Schedule a microtask to give the cache a chance to add the key. 134 | scheduleMicrotask(() { 135 | PaintingBinding.instance.imageCache.evict(key); 136 | }); 137 | 138 | errorListener?.call(); 139 | rethrow; 140 | } finally { 141 | await chunkEvents.close(); 142 | } 143 | } 144 | 145 | @override 146 | bool operator ==(dynamic other) { 147 | if (other is OptimizedCacheImageProvider) { 148 | return ((cacheKey ?? url) == (other.cacheKey ?? other.url)) && 149 | scale == other.scale && 150 | maxHeight == other.maxHeight && 151 | maxWidth == other.maxWidth; 152 | } 153 | return false; 154 | } 155 | 156 | @override 157 | int get hashCode => Object.hash(cacheKey ?? url, scale, maxHeight, maxWidth); 158 | 159 | @override 160 | String toString() => '$runtimeType("$url", scale: $scale)'; 161 | } 162 | -------------------------------------------------------------------------------- /lib/src/image_provider/_image_provider_web.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:ui' as ui; 3 | import 'dart:ui'; 4 | 5 | import 'package:flutter/foundation.dart'; 6 | import 'package:flutter/painting.dart'; 7 | import 'package:flutter_cache_manager/flutter_cache_manager.dart'; 8 | 9 | import '../../optimized_cached_image.dart' show ImageRenderMethodForWeb; 10 | import '_load_async_web.dart'; 11 | import 'multi_image_stream_completer.dart'; 12 | import 'optimized_cached_image_provider.dart' as image_provider; 13 | 14 | /// The dart:html implementation of [image_provider.OptimizedCacheImageProvider]. 15 | class OptimizedCacheImageProvider 16 | extends ImageProvider 17 | implements image_provider.OptimizedCacheImageProvider { 18 | /// Creates an object that fetches the image at the given URL. 19 | /// 20 | /// The arguments [url] and [scale] must not be null. 21 | const OptimizedCacheImageProvider( 22 | this.url, { 23 | this.maxHeight, 24 | this.maxWidth, 25 | this.scale = 1.0, 26 | this.errorListener, 27 | this.headers, 28 | this.cacheManager, 29 | this.cacheKey, 30 | ImageRenderMethodForWeb? imageRenderMethodForWeb, 31 | }) : _imageRenderMethodForWeb = 32 | imageRenderMethodForWeb ?? ImageRenderMethodForWeb.HtmlImage; 33 | 34 | @override 35 | final BaseCacheManager? cacheManager; 36 | 37 | @override 38 | final String url; 39 | 40 | @override 41 | final String? cacheKey; 42 | 43 | @override 44 | final double scale; 45 | 46 | /// Listener to be called when images fails to load. 47 | @override 48 | final image_provider.ErrorListener? errorListener; 49 | 50 | @override 51 | final Map? headers; 52 | 53 | @override 54 | final int? maxHeight; 55 | 56 | @override 57 | final int? maxWidth; 58 | 59 | final ImageRenderMethodForWeb _imageRenderMethodForWeb; 60 | 61 | @override 62 | Future obtainKey( 63 | ImageConfiguration configuration) { 64 | return SynchronousFuture(this); 65 | } 66 | 67 | @override 68 | ImageStreamCompleter loadImage( 69 | image_provider.OptimizedCacheImageProvider key, 70 | ImageDecoderCallback decode, 71 | ) { 72 | final chunkEvents = StreamController(); 73 | 74 | return MultiImageStreamCompleter( 75 | chunkEvents: chunkEvents.stream, 76 | codec: 77 | _loadAsync(key as OptimizedCacheImageProvider, chunkEvents, decode), 78 | scale: key.scale, 79 | informationCollector: _imageStreamInformationCollector(key)); 80 | } 81 | 82 | InformationCollector? _imageStreamInformationCollector( 83 | image_provider.OptimizedCacheImageProvider key) { 84 | InformationCollector? collector; 85 | assert(() { 86 | collector = () { 87 | return [ 88 | DiagnosticsProperty('Image provider', this), 89 | DiagnosticsProperty( 90 | 'Image key', key as OptimizedCacheImageProvider), 91 | ]; 92 | }; 93 | return true; 94 | }()); 95 | return collector; 96 | } 97 | 98 | Stream _loadAsync( 99 | OptimizedCacheImageProvider key, 100 | StreamController chunkEvents, 101 | ImageDecoderCallback decode, 102 | ) { 103 | switch (_imageRenderMethodForWeb) { 104 | case ImageRenderMethodForWeb.HttpGet: 105 | return _loadAsyncHttpGet(key, chunkEvents, decode); 106 | case ImageRenderMethodForWeb.HtmlImage: 107 | return loadAsyncHtmlImage(key, chunkEvents, decode).asStream(); 108 | } 109 | } 110 | 111 | Stream _loadAsyncHttpGet( 112 | OptimizedCacheImageProvider key, 113 | StreamController chunkEvents, 114 | ImageDecoderCallback decode, 115 | ) async* { 116 | assert(key == this); 117 | try { 118 | var mngr = cacheManager ?? DefaultCacheManager(); 119 | await for (var result in mngr.getFileStream(key.url, 120 | withProgress: true, headers: headers)) { 121 | if (result is DownloadProgress) { 122 | chunkEvents.add(ImageChunkEvent( 123 | cumulativeBytesLoaded: result.downloaded, 124 | expectedTotalBytes: result.totalSize, 125 | )); 126 | } 127 | if (result is FileInfo) { 128 | var file = result.file; 129 | var bytes = 130 | await ImmutableBuffer.fromUint8List(await file.readAsBytes()); 131 | var decoded = await decode(bytes); 132 | yield decoded; 133 | } 134 | } 135 | } catch (e) { 136 | // Depending on where the exception was thrown, the image cache may not 137 | // have had a chance to track the key in the cache at all. 138 | // Schedule a microtask to give the cache a chance to add the key. 139 | scheduleMicrotask(() { 140 | PaintingBinding.instance.imageCache.evict(key); 141 | }); 142 | 143 | errorListener?.call(); 144 | rethrow; 145 | } finally { 146 | await chunkEvents.close(); 147 | } 148 | } 149 | 150 | @override 151 | bool operator ==(Object other) { 152 | if (other.runtimeType != runtimeType) { 153 | return false; 154 | } 155 | if (other is OptimizedCacheImageProvider) { 156 | var sameKey = (cacheKey ?? url) == (other.cacheKey ?? other.url); 157 | return sameKey && scale == other.scale; 158 | } else { 159 | return false; 160 | } 161 | } 162 | 163 | @override 164 | int get hashCode => Object.hash(url, scale, cacheKey); 165 | 166 | @override 167 | String toString() => '$runtimeType("$url", scale: $scale)'; 168 | } 169 | -------------------------------------------------------------------------------- /lib/src/image_provider/_load_async_web.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:ui' as ui; 3 | 4 | import 'package:flutter/rendering.dart'; 5 | 6 | import '../../optimized_cached_image.dart'; 7 | 8 | /// Method to load html images using the webOnlyInstantiateImageCodecFromUrl. 9 | /// This method is not recognized by the flutter analyzer. 10 | Future loadAsyncHtmlImage( 11 | OptimizedCacheImageProvider key, 12 | StreamController chunkEvents, 13 | ImageDecoderCallback decode, 14 | ) { 15 | final resolved = Uri.base.resolve(key.url); 16 | // ignore: undefined_function 17 | return ui.webOnlyInstantiateImageCodecFromUrl( 18 | resolved, 19 | chunkCallback: (int bytes, int total) { 20 | chunkEvents.add( 21 | ImageChunkEvent( 22 | cumulativeBytesLoaded: bytes, 23 | expectedTotalBytes: total, 24 | ), 25 | ); 26 | }, 27 | ) as Future; 28 | } 29 | -------------------------------------------------------------------------------- /lib/src/image_provider/multi_image_stream_completer.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:ui' as ui show Codec, FrameInfo; 3 | 4 | import 'package:flutter/foundation.dart'; 5 | import 'package:flutter/painting.dart'; 6 | import 'package:flutter/scheduler.dart'; 7 | 8 | /// Slows down animations by this factor to help in development. 9 | double get timeDilation => _timeDilation; 10 | double _timeDilation = 1.0; 11 | 12 | /// An ImageStreamCompleter with support for loading multiple images. 13 | class MultiImageStreamCompleter extends ImageStreamCompleter { 14 | /// The constructor to create an MultiImageStreamCompleter. The [codec] 15 | /// should be a stream with the images that should be shown. The 16 | /// [chunkEvents] should indicate the [ImageChunkEvent]s of the first image 17 | /// to show. 18 | MultiImageStreamCompleter({ 19 | required Stream codec, 20 | required double scale, 21 | Stream? chunkEvents, 22 | InformationCollector? informationCollector, 23 | }) : _informationCollector = informationCollector, 24 | _scale = scale { 25 | codec.listen((event) { 26 | if (_timer != null) { 27 | _nextImageCodec = event; 28 | } else { 29 | _handleCodecReady(event); 30 | } 31 | }, onError: (dynamic error, StackTrace stack) { 32 | reportError( 33 | context: ErrorDescription('resolving an image codec'), 34 | exception: error, 35 | stack: stack, 36 | informationCollector: informationCollector, 37 | silent: true, 38 | ); 39 | }); 40 | if (chunkEvents != null) { 41 | chunkEvents.listen( 42 | reportImageChunkEvent, 43 | onError: (dynamic error, StackTrace stack) { 44 | reportError( 45 | context: ErrorDescription('loading an image'), 46 | exception: error, 47 | stack: stack, 48 | informationCollector: informationCollector, 49 | silent: true, 50 | ); 51 | }, 52 | ); 53 | } 54 | } 55 | 56 | ui.Codec? _codec; 57 | ui.Codec? _nextImageCodec; 58 | final double _scale; 59 | final InformationCollector? _informationCollector; 60 | ui.FrameInfo? _nextFrame; 61 | // When the current was first shown. 62 | Duration? _shownTimestamp; 63 | // The requested duration for the current frame; 64 | Duration? _frameDuration; 65 | // How many frames have been emitted so far. 66 | int _framesEmitted = 0; 67 | Timer? _timer; 68 | 69 | // Used to guard against registering multiple _handleAppFrame callbacks for the same frame. 70 | bool _frameCallbackScheduled = false; 71 | 72 | void _switchToNewCodec() { 73 | _framesEmitted = 0; 74 | _timer = null; 75 | _handleCodecReady(_nextImageCodec!); 76 | _nextImageCodec = null; 77 | } 78 | 79 | void _handleCodecReady(ui.Codec codec) { 80 | _codec = codec; 81 | assert(_codec != null); 82 | 83 | if (hasListeners) { 84 | _decodeNextFrameAndSchedule(); 85 | } 86 | } 87 | 88 | void _handleAppFrame(Duration timestamp) { 89 | _frameCallbackScheduled = false; 90 | if (!hasListeners) return; 91 | if (_isFirstFrame() || _hasFrameDurationPassed(timestamp)) { 92 | _emitFrame(ImageInfo(image: _nextFrame!.image, scale: _scale)); 93 | _shownTimestamp = timestamp; 94 | _frameDuration = _nextFrame!.duration; 95 | _nextFrame = null; 96 | if (_framesEmitted % _codec!.frameCount == 0 && _nextImageCodec != null) { 97 | _switchToNewCodec(); 98 | } else { 99 | final completedCycles = _framesEmitted ~/ _codec!.frameCount; 100 | if (_codec!.repetitionCount == -1 || 101 | completedCycles <= _codec!.repetitionCount) { 102 | _decodeNextFrameAndSchedule(); 103 | } 104 | } 105 | return; 106 | } 107 | final delay = _frameDuration! - (timestamp - _shownTimestamp!); 108 | _timer = Timer(delay * timeDilation, _scheduleAppFrame); 109 | } 110 | 111 | bool _isFirstFrame() { 112 | return _frameDuration == null; 113 | } 114 | 115 | bool _hasFrameDurationPassed(Duration timestamp) { 116 | assert(_shownTimestamp != null); 117 | return timestamp - _shownTimestamp! >= _frameDuration!; 118 | } 119 | 120 | Future _decodeNextFrameAndSchedule() async { 121 | try { 122 | _nextFrame = await _codec!.getNextFrame(); 123 | } catch (exception, stack) { 124 | reportError( 125 | context: ErrorDescription('resolving an image frame'), 126 | exception: exception, 127 | stack: stack, 128 | informationCollector: _informationCollector, 129 | silent: true, 130 | ); 131 | return; 132 | } 133 | if (_codec!.frameCount == 1) { 134 | // ImageStreamCompleter listeners removed while waiting for next frame to 135 | // be decoded. 136 | // There's no reason to emit the frame without active listeners. 137 | if (!hasListeners) { 138 | return; 139 | } 140 | 141 | // This is not an animated image, just return it and don't schedule more 142 | // frames. 143 | _emitFrame(ImageInfo(image: _nextFrame!.image, scale: _scale)); 144 | return; 145 | } 146 | _scheduleAppFrame(); 147 | } 148 | 149 | void _scheduleAppFrame() { 150 | if (_frameCallbackScheduled) { 151 | return; 152 | } 153 | _frameCallbackScheduled = true; 154 | SchedulerBinding.instance.scheduleFrameCallback(_handleAppFrame); 155 | } 156 | 157 | void _emitFrame(ImageInfo imageInfo) { 158 | setImage(imageInfo); 159 | _framesEmitted += 1; 160 | } 161 | 162 | @override 163 | void addListener(ImageStreamListener listener) { 164 | if (!hasListeners && _codec != null) _decodeNextFrameAndSchedule(); 165 | super.addListener(listener); 166 | } 167 | 168 | @override 169 | void removeListener(ImageStreamListener listener) { 170 | super.removeListener(listener); 171 | if (!hasListeners) { 172 | _timer?.cancel(); 173 | _timer = null; 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /lib/src/image_provider/optimized_cached_image_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/painting.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:flutter_cache_manager/flutter_cache_manager.dart'; 4 | 5 | import '_image_provider_io.dart' 6 | if (dart.library.html) '_image_provider_web.dart' as image_provider; 7 | 8 | /// Function which is called after loading the image failed. 9 | typedef ErrorListener = void Function(); 10 | 11 | /// Currently there are 2 different ways to show an image on the web with both 12 | /// their own pros and cons, using a custom [HttpGet] 13 | /// or an HTML Image element mentioned [here on a GitHub issue](https://github.com/flutter/flutter/issues/57187#issuecomment-635637494). 14 | /// 15 | /// When using HttpGet the image will work on Skia and it will use the [OptimizedCacheImageProvider.headers] 16 | /// when they are provided. In this package it also uses any url transformations that might 17 | /// be executed by the [OptimizedCacheImageProvider.cacheManager]. However, this method does require a CORS 18 | /// handshake and will not just work for every image from the web. 19 | /// 20 | /// The [HtmlImage] does not need a CORS handshake, but it also does not use your 21 | /// provided headers and it does not work when using Skia to render the page. 22 | enum ImageRenderMethodForWeb { 23 | /// HtmlImage uses a default web image including default browser caching. 24 | /// This is the recommended and default choice. 25 | HtmlImage, 26 | 27 | /// HttpGet uses an http client to fetch an image. It enables the use of 28 | /// headers, but loses some default web functionality. 29 | HttpGet, 30 | } 31 | 32 | /// An ImageProvider to load images from the network with caching functionality. 33 | abstract class OptimizedCacheImageProvider 34 | extends ImageProvider { 35 | /// Creates an object that fetches the image at the given URL. 36 | /// 37 | /// The arguments [url] and [scale] must not be null. 38 | /// The [imageRenderMethodForWeb] defines the behavior of the ImageProvider 39 | /// when compiled for web. See the documentation of [ImageRenderMethodForWeb] 40 | /// for the benefits of each method. 41 | const factory OptimizedCacheImageProvider( 42 | String url, { 43 | int? maxHeight, 44 | int? maxWidth, 45 | String? cacheKey, 46 | double scale, 47 | @Deprecated('ErrorListener is deprecated, use listeners on the imagestream') 48 | ErrorListener? errorListener, 49 | Map? headers, 50 | BaseCacheManager? cacheManager, 51 | ImageRenderMethodForWeb? imageRenderMethodForWeb, 52 | }) = image_provider.OptimizedCacheImageProvider; 53 | 54 | /// Optional cache manager. If no cache manager is defined DefaultCacheManager() 55 | /// will be used. 56 | /// 57 | /// When running flutter on the web, the cacheManager is not used. 58 | BaseCacheManager? get cacheManager; 59 | 60 | /// The errorListener is called when the ImageProvider failed loading the 61 | /// image. Deprecated in favor of [ImageStreamListener.onError]. 62 | @deprecated 63 | ErrorListener? get errorListener; 64 | 65 | /// The URL from which the image will be fetched. 66 | String get url; 67 | 68 | /// The Key from image for cache 69 | String? get cacheKey; 70 | 71 | /// The scale to place in the [ImageInfo] object of the image. 72 | double get scale; 73 | 74 | /// The HTTP headers that will be used to fetch image from network. 75 | Map? get headers; 76 | 77 | /// Max height in pixels for the image. When set the resized image is 78 | /// stored in the cache. 79 | int? get maxHeight; 80 | 81 | /// Max width in pixels for the image. When set the resized image is 82 | /// stored in the cache. 83 | int? get maxWidth; 84 | 85 | @override 86 | ImageStreamCompleter loadImage( 87 | OptimizedCacheImageProvider key, ImageDecoderCallback decode); 88 | } 89 | -------------------------------------------------------------------------------- /lib/src/oci_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:optimized_cached_image/optimized_cached_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/widgets.dart'; 4 | import 'package:flutter_cache_manager/flutter_cache_manager.dart'; 5 | import 'package:octo_image/octo_image.dart'; 6 | 7 | /// Builder function to create an image widget. The function is called after 8 | /// the ImageProvider completes the image loading. 9 | typedef ImageWidgetBuilder = Widget Function( 10 | BuildContext context, 11 | ImageProvider imageProvider, 12 | ); 13 | 14 | /// Builder function to create a placeholder widget. The function is called 15 | /// once while the ImageProvider is loading the image. 16 | typedef PlaceholderWidgetBuilder = Widget Function( 17 | BuildContext context, 18 | String url, 19 | ); 20 | 21 | /// Builder function to create a progress indicator widget. The function is 22 | /// called every time a chuck of the image is downloaded from the web, but at 23 | /// least once during image loading. 24 | typedef ProgressIndicatorBuilder = Widget Function( 25 | BuildContext context, 26 | String url, 27 | DownloadProgress progress, 28 | ); 29 | 30 | /// Builder function to create an error widget. This builder is called when 31 | /// the image failed loading, for example due to a 404 NotFound exception. 32 | typedef LoadingErrorWidgetBuilder = Widget Function( 33 | BuildContext context, 34 | String url, 35 | dynamic error, 36 | ); 37 | 38 | /// Image widget to show NetworkImage with caching functionality. 39 | // ignore: must_be_immutable 40 | class OptimizedCacheImage extends StatelessWidget { 41 | /// Evict an image from both the disk file based caching system of the 42 | /// [BaseCacheManager] as the in memory [ImageCache] of the [ImageProvider]. 43 | /// [url] is used by both the disk and memory cache. The scale is only used 44 | /// to clear the image from the [ImageCache]. 45 | static Future evictFromCache( 46 | String url, { 47 | String? cacheKey, 48 | BaseCacheManager? cacheManager, 49 | double scale = 1.0, 50 | }) async { 51 | cacheManager = cacheManager ?? DefaultCacheManager(); 52 | await cacheManager.removeFile(cacheKey ?? url); 53 | return OptimizedCacheImageProvider(url, scale: scale).evict(); 54 | } 55 | 56 | OptimizedCacheImageProvider? _image; 57 | 58 | /// Option to use cachemanager with other settings 59 | final BaseCacheManager? cacheManager; 60 | 61 | /// The target image that is displayed. 62 | final String imageUrl; 63 | 64 | /// The target image's cache key. 65 | final String? cacheKey; 66 | 67 | /// Optional builder to further customize the display of the image. 68 | final ImageWidgetBuilder? imageBuilder; 69 | 70 | /// Widget displayed while the target [imageUrl] is loading. 71 | final PlaceholderWidgetBuilder? placeholder; 72 | 73 | /// Widget displayed while the target [imageUrl] is loading. 74 | final ProgressIndicatorBuilder? progressIndicatorBuilder; 75 | 76 | /// Widget displayed while the target [imageUrl] failed loading. 77 | final LoadingErrorWidgetBuilder? errorWidget; 78 | 79 | /// The duration of the fade-in animation for the [placeholder]. 80 | final Duration? placeholderFadeInDuration; 81 | 82 | /// The duration of the fade-out animation for the [placeholder]. 83 | final Duration? fadeOutDuration; 84 | 85 | /// The curve of the fade-out animation for the [placeholder]. 86 | final Curve fadeOutCurve; 87 | 88 | /// The duration of the fade-in animation for the [imageUrl]. 89 | final Duration fadeInDuration; 90 | 91 | /// The curve of the fade-in animation for the [imageUrl]. 92 | final Curve fadeInCurve; 93 | 94 | /// If non-null, require the image to have this width. 95 | /// 96 | /// If null, the image will pick a size that best preserves its intrinsic 97 | /// aspect ratio. This may result in a sudden change if the size of the 98 | /// placeholder widget does not match that of the target image. The size is 99 | /// also affected by the scale factor. 100 | final double? width; 101 | 102 | /// If non-null, require the image to have this height. 103 | /// 104 | /// If null, the image will pick a size that best preserves its intrinsic 105 | /// aspect ratio. This may result in a sudden change if the size of the 106 | /// placeholder widget does not match that of the target image. The size is 107 | /// also affected by the scale factor. 108 | final double? height; 109 | 110 | /// How to inscribe the image into the space allocated during layout. 111 | /// 112 | /// The default varies based on the other fields. See the discussion at 113 | /// [paintImage]. 114 | final BoxFit? fit; 115 | 116 | /// How to align the image within its bounds. 117 | /// 118 | /// The alignment aligns the given position in the image to the given position 119 | /// in the layout bounds. For example, a [Alignment] alignment of (-1.0, 120 | /// -1.0) aligns the image to the top-left corner of its layout bounds, while a 121 | /// [Alignment] alignment of (1.0, 1.0) aligns the bottom right of the 122 | /// image with the bottom right corner of its layout bounds. Similarly, an 123 | /// alignment of (0.0, 1.0) aligns the bottom middle of the image with the 124 | /// middle of the bottom edge of its layout bounds. 125 | /// 126 | /// If the [alignment] is [TextDirection]-dependent (i.e. if it is a 127 | /// [AlignmentDirectional]), then an ambient [Directionality] widget 128 | /// must be in scope. 129 | /// 130 | /// Defaults to [Alignment.center]. 131 | /// 132 | /// See also: 133 | /// 134 | /// * [Alignment], a class with convenient constants typically used to 135 | /// specify an [AlignmentGeometry]. 136 | /// * [AlignmentDirectional], like [Alignment] for specifying alignments 137 | /// relative to text direction. 138 | final AlignmentGeometry alignment; 139 | 140 | /// How to paint any portions of the layout bounds not covered by the image. 141 | final ImageRepeat repeat; 142 | 143 | /// Whether to paint the image in the direction of the [TextDirection]. 144 | /// 145 | /// If this is true, then in [TextDirection.ltr] contexts, the image will be 146 | /// drawn with its origin in the top left (the "normal" painting direction for 147 | /// children); and in [TextDirection.rtl] contexts, the image will be drawn with 148 | /// a scaling factor of -1 in the horizontal direction so that the origin is 149 | /// in the top right. 150 | /// 151 | /// This is occasionally used with children in right-to-left environments, for 152 | /// children that were designed for left-to-right locales. Be careful, when 153 | /// using this, to not flip children with integral shadows, text, or other 154 | /// effects that will look incorrect when flipped. 155 | /// 156 | /// If this is true, there must be an ambient [Directionality] widget in 157 | /// scope. 158 | final bool matchTextDirection; 159 | 160 | /// Optional headers for the http request of the image url 161 | final Map? httpHeaders; 162 | 163 | /// When set to true it will animate from the old image to the new image 164 | /// if the url changes. 165 | final bool useOldImageOnUrlChange; 166 | 167 | /// If non-null, this color is blended with each image pixel using [colorBlendMode]. 168 | final Color? color; 169 | 170 | /// Used to combine [color] with this image. 171 | /// 172 | /// The default is [BlendMode.srcIn]. In terms of the blend mode, [color] is 173 | /// the source and this image is the destination. 174 | /// 175 | /// See also: 176 | /// 177 | /// * [BlendMode], which includes an illustration of the effect of each blend mode. 178 | final BlendMode? colorBlendMode; 179 | 180 | /// Target the interpolation quality for image scaling. 181 | /// 182 | /// If not given a value, defaults to FilterQuality.low. 183 | final FilterQuality filterQuality; 184 | 185 | /// Will resize the image in memory to have a certain width using [ResizeImage] 186 | final int? memCacheWidth; 187 | 188 | /// Will resize the image in memory to have a certain height using [ResizeImage] 189 | final int? memCacheHeight; 190 | 191 | /// Will resize the image and store the resized image in the disk cache. 192 | final int? maxWidthDiskCache; 193 | 194 | /// Will resize the image and store the resized image in the disk cache. 195 | final int? maxHeightDiskCache; 196 | 197 | final ImageRenderMethodForWeb? imageRenderMethodForWeb; 198 | 199 | /// OptimizedCacheImage shows a network image using a caching mechanism. It also 200 | /// provides support for a placeholder, showing an error and fading into the 201 | /// loaded image. Next to that it supports most features of a default Image 202 | /// widget. 203 | OptimizedCacheImage({ 204 | Key? key, 205 | required this.imageUrl, 206 | this.httpHeaders, 207 | this.imageBuilder, 208 | this.placeholder, 209 | this.progressIndicatorBuilder, 210 | this.errorWidget, 211 | this.fadeOutDuration = const Duration(milliseconds: 1000), 212 | this.fadeOutCurve = Curves.easeOut, 213 | this.fadeInDuration = const Duration(milliseconds: 500), 214 | this.fadeInCurve = Curves.easeIn, 215 | this.width, 216 | this.height, 217 | this.fit, 218 | this.alignment = Alignment.center, 219 | this.repeat = ImageRepeat.noRepeat, 220 | this.matchTextDirection = false, 221 | this.cacheManager, 222 | this.useOldImageOnUrlChange = false, 223 | this.color, 224 | this.filterQuality = FilterQuality.low, 225 | this.colorBlendMode, 226 | this.placeholderFadeInDuration, 227 | this.memCacheWidth, 228 | this.memCacheHeight, 229 | this.cacheKey, 230 | this.maxWidthDiskCache, 231 | this.maxHeightDiskCache, 232 | this.imageRenderMethodForWeb, 233 | }) : super(key: key); 234 | 235 | @override 236 | Widget build(BuildContext context) { 237 | var octoPlaceholderBuilder = 238 | placeholder != null ? _octoPlaceholderBuilder : null; 239 | var octoProgressIndicatorBuilder = 240 | progressIndicatorBuilder != null ? _octoProgressIndicatorBuilder : null; 241 | 242 | ///If there is no placeholder OctoImage does not fade, so always set an 243 | ///(empty) placeholder as this always used to be the behaviour of 244 | ///OptimizedCacheImage. 245 | if (octoPlaceholderBuilder == null && 246 | octoProgressIndicatorBuilder == null) { 247 | octoPlaceholderBuilder = (context) => Container(); 248 | } 249 | 250 | return LayoutBuilder(builder: (ctx, constraints) { 251 | int? _constrainWidth = width?.toInt() ?? maxWidthDiskCache; 252 | int? _constrainHeight = height?.toInt() ?? maxHeightDiskCache; 253 | 254 | if (_constrainWidth == null && _constrainHeight == null) { 255 | _constrainWidth = constraints.maxWidth != double.infinity 256 | ? constraints.maxWidth.toInt() 257 | : null; 258 | _constrainHeight = constraints.maxHeight != double.infinity 259 | ? constraints.maxHeight.toInt() 260 | : null; 261 | } 262 | 263 | final ratio = MediaQuery.of(context).devicePixelRatio; 264 | _constrainWidth = 265 | _constrainWidth != null ? (_constrainWidth * ratio).toInt() : null; 266 | _constrainHeight = 267 | _constrainHeight != null ? (_constrainHeight * ratio).toInt() : null; 268 | 269 | if (_image == null || 270 | _image?.maxHeight != _constrainHeight || 271 | _image?.maxWidth != _constrainHeight) { 272 | _image = OptimizedCacheImageProvider( 273 | imageUrl, 274 | headers: httpHeaders, 275 | cacheManager: cacheManager, 276 | cacheKey: cacheKey, 277 | imageRenderMethodForWeb: imageRenderMethodForWeb, 278 | maxWidth: _constrainWidth, 279 | maxHeight: _constrainHeight, 280 | ); 281 | } 282 | return OctoImage( 283 | image: _image!, 284 | imageBuilder: imageBuilder != null ? _octoImageBuilder : null, 285 | placeholderBuilder: octoPlaceholderBuilder, 286 | progressIndicatorBuilder: octoProgressIndicatorBuilder, 287 | errorBuilder: errorWidget != null ? _octoErrorBuilder : null, 288 | fadeOutDuration: fadeOutDuration, 289 | fadeOutCurve: fadeOutCurve, 290 | fadeInDuration: fadeInDuration, 291 | fadeInCurve: fadeInCurve, 292 | width: width, 293 | height: height, 294 | fit: fit, 295 | alignment: alignment as Alignment?, 296 | repeat: repeat, 297 | matchTextDirection: matchTextDirection, 298 | color: color, 299 | filterQuality: filterQuality, 300 | colorBlendMode: colorBlendMode, 301 | placeholderFadeInDuration: placeholderFadeInDuration, 302 | gaplessPlayback: useOldImageOnUrlChange, 303 | ); 304 | }); 305 | } 306 | 307 | Widget _octoImageBuilder(BuildContext context, Widget child) { 308 | return imageBuilder!(context, _image!); 309 | } 310 | 311 | Widget _octoPlaceholderBuilder(BuildContext context) { 312 | return placeholder!(context, imageUrl); 313 | } 314 | 315 | Widget _octoProgressIndicatorBuilder( 316 | BuildContext context, 317 | ImageChunkEvent? progress, 318 | ) { 319 | int? totalSize; 320 | var downloaded = 0; 321 | if (progress != null) { 322 | totalSize = progress.expectedTotalBytes; 323 | downloaded = progress.cumulativeBytesLoaded; 324 | } 325 | return progressIndicatorBuilder!( 326 | context, imageUrl, DownloadProgress(imageUrl, totalSize, downloaded)); 327 | } 328 | 329 | Widget _octoErrorBuilder( 330 | BuildContext context, 331 | Object error, 332 | StackTrace? stackTrace, 333 | ) { 334 | return errorWidget!(context, imageUrl, error); 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /lib/src/transformer/image_transformer.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:file/local.dart' as fileIo; 5 | import 'package:flutter_cache_manager/flutter_cache_manager.dart' show FileInfo; 6 | import 'package:flutter_image_compress/flutter_image_compress.dart'; 7 | import 'package:path/path.dart' as p; 8 | import 'package:sprintf/sprintf.dart'; 9 | 10 | import '../debug_tools.dart'; 11 | import 'scale_info.dart'; 12 | 13 | class DefaultImageTransformer extends ImageTransformer { 14 | DefaultImageTransformer(); 15 | 16 | final tmpFileSuffix = '_w%d_h%d'; 17 | 18 | final _compressionFormats = { 19 | '.jpg': CompressFormat.jpeg, 20 | '.jpeg': CompressFormat.jpeg, 21 | '.webp': CompressFormat.webp, 22 | '.gif': CompressFormat.webp, 23 | '.png': CompressFormat.png, 24 | '.heic': CompressFormat.heic 25 | }; 26 | final _extensionFormats = { 27 | CompressFormat.jpeg: '.jpg', 28 | CompressFormat.webp: '.webp', 29 | CompressFormat.png: '.png', 30 | CompressFormat.heic: '.heic' 31 | }; 32 | 33 | @override 34 | Future transform(FileInfo info, int? width, int? height) async { 35 | final value = await _scaleImageFile(info, width, height); 36 | return value; 37 | } 38 | 39 | Future _scaleImageFile( 40 | FileInfo info, int? width, int? height) async { 41 | FileInfo fileInfo = info; 42 | final file = fileInfo.file; 43 | log("Scaling file.. ${fileInfo.originalUrl}"); 44 | var resizedFile = file; 45 | final canResize = file.existsSync() && (width != null || height != null); 46 | if (canResize) { 47 | final scaleInfo = getScaledFileInfo(file, width, height); 48 | final srcSize = file.lengthSync(); 49 | log("Dimensions width=${scaleInfo.width}, height=${scaleInfo.height}, format ${scaleInfo.compressFormat}"); 50 | await FlutterImageCompress.compressAndGetFile( 51 | file.path, scaleInfo.file.path, 52 | minWidth: scaleInfo.width, 53 | minHeight: scaleInfo.height, 54 | format: scaleInfo.compressFormat, 55 | quality: 90); 56 | final localFileSystem = fileIo.LocalFileSystem(); 57 | resizedFile = localFileSystem.file(scaleInfo.file.path); 58 | 59 | if (resizedFile.existsSync()) { 60 | if (resizedFile.lengthSync() < srcSize) { 61 | log("Resized success ${fileInfo.originalUrl}"); 62 | } else { 63 | log("Resized image is bigger, deleting and using original ${fileInfo.originalUrl}"); 64 | resizedFile.deleteSync(); 65 | resizedFile = file; 66 | } 67 | } else { 68 | resizedFile = file; 69 | log("Resize Failure for ${fileInfo.originalUrl}"); 70 | } 71 | } 72 | fileInfo = FileInfo( 73 | resizedFile, fileInfo.source, fileInfo.validTill, fileInfo.originalUrl); 74 | return fileInfo; 75 | } 76 | 77 | @override 78 | ScaleInfo getScaledFileInfo(File file, int? width, int? height) { 79 | final format = _getCompressionFormat(file); 80 | 81 | final directory = file.parent; 82 | final destPath = directory.path + 83 | "/" + 84 | p.basenameWithoutExtension(file.path) + 85 | sprintf(tmpFileSuffix, [width ?? 1, height ?? 1]) + 86 | _extensionFormats[format]!; 87 | final scaleFile = File(destPath); 88 | return ScaleInfo(scaleFile, width ?? 1, height ?? 1, format); 89 | } 90 | 91 | CompressFormat _getCompressionFormat(File file) { 92 | String extension = p.extension(file.path); 93 | return _compressionFormats[extension] ?? CompressFormat.png; 94 | } 95 | } 96 | 97 | abstract class ImageTransformer { 98 | Future transform(FileInfo info, int? width, int? height); 99 | ScaleInfo getScaledFileInfo(File file, int width, int height); 100 | } 101 | -------------------------------------------------------------------------------- /lib/src/transformer/scale_info.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter_image_compress/flutter_image_compress.dart'; 4 | 5 | class ScaleInfo { 6 | final File file; 7 | final int width; 8 | final int height; 9 | final CompressFormat compressFormat; 10 | 11 | const ScaleInfo(this.file, this.width, this.height, this.compressFormat); 12 | } 13 | -------------------------------------------------------------------------------- /optimized_cached_image.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: optimized_cached_image 2 | description: A library for loading images from network, resizing as per container size and caching while being memory sensitive. 3 | version: 3.0.1 4 | homepage: https://github.com/humblerookie/optimized_cached_image 5 | 6 | environment: 7 | sdk: ">=3.0.0 <4.0.0" 8 | dependencies: 9 | flutter: 10 | sdk: flutter 11 | flutter_cache_manager: ^3.3.0 12 | flutter_image_compress: ^2.0.3 13 | uuid: ^3.0.3 14 | http: ^0.13.1 15 | path_provider: ^2.0.15 16 | path: ^1.8.0 17 | pedantic: ^1.11.0 18 | sprintf: ^7.0.0 19 | octo_image: ^1.0.0 20 | image: ^4.0.17 21 | file: ^6.1.0 22 | dev_dependencies: 23 | test: ^1.16.8 -------------------------------------------------------------------------------- /screenshots/streamed_vs_nonstreamed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/humblerookie/optimized_cached_image/c760d1d3a1b525cc1e71c93a808135d1f10d4d1e/screenshots/streamed_vs_nonstreamed.jpg -------------------------------------------------------------------------------- /scripts/checks.sh: -------------------------------------------------------------------------------- 1 | dartfmt -w --fix .; 2 | 3 | --------------------------------------------------------------------------------